R/monad.R

#' Monad Operators and Generics
#'
#' Classes implementing methods for these S7 generics are called monads.
#' `fmap()` should be implemented such that the \link[=functor-laws]{functor
#' laws} hold. `bind()` or `join()` should be implemented such that the
#' \link[=monad-laws]{monad laws} hold. `%>>%` is the `fmap()` pipe operator,
#' and `%>-%` is the `bind()` pipe operator. Operator usage is in the form `m
#' %>>% f(...)`.
#'
#' @section Details:
#'
#'   Monads are containers for values. `fmap()` transforms the contained value
#'   with a function. `bind()` transforms the contained value with a function
#'   that returns a monadic object. `join()` takes a monad whose contained value
#'   is another monad, and combines them into a new monadic object. It's used to
#'   unwrap a layer of monadic structure. Implementing classes typically embed
#'   some form of control flow or state management in `bind()` or `join()`.
#'
#'   There's a default implementation for `join()` if you provide `bind()`, and
#'   there's a default implementation for `bind()` if you provide `join()` and
#'   `fmap()`. For performance reasons you may wish to implement both
#'   regardless.
#'
#' @section Operators:
#'
#'   The pipe operators expect a monadic object as `lhs` and a function or a
#'   call expression as `rhs`. A call in `rhs` is treated as partial application
#'   of the function `f`. The pipe expression is transformed into a call to the
#'   corresponding monad generic with any call arguments in `rhs` passed as
#'   additional arguments to `f` in the generic. For example, `m %>>% f(x)` is
#'   equivalent to `fmap(m, f, x)` and `m %>-% f(x)` is equivalent to `bind(m,
#'   f, x)`.
#'
#' @section Trivia:
#'
#'   A class that only implements `fmap()` is called a functor.
#'
#' @param m,lhs A monadic object.
#' @param f,rhs A function. For `bind()`, it should return a monadic object.
#' @param ... Additional arguments passed to `f`.
#'
#' @returns A monadic object.
#' @seealso The \link[=monad-laws]{monad laws} and \link[=functor-laws]{functor
#'   laws} that implementations should satisfy.
#' @seealso [List] and [Maybe] for examples of implementing classes.
#'
#' @examples
#' # We demonstrate by implementing a simple Either monad.
#' library(S7)
#'
#' # Start by defining constructors of the Left and Right variants. Conventionally
#' # a Right variant signifies success and Left an error condition with a context.
#' left <- function(x) structure(list(value = x), class = c("left", "either"))
#' right <- function(x) structure(list(value = x), class = c("right", "either"))
#'
#' # Implement fmap() and bind() methods to gain access to monad operators.
#' class_either <- new_S3_class("either")
#'
#' method(fmap, class_either) <- function(m, f, ...) {
#'   if (inherits(m, "left")) m else right(f(m$value))
#' }
#'
#' method(bind, class_either) <- function(m, f, ...) {
#'   if (inherits(m, "left")) m else f(m$value)
#' }
#'
#' # Use with your function that handles errors by returning a monadic value.
#' mlog <- function(x) {
#'   if (x > 0) right(log(x)) else left("`x` must be strictly positive.")
#' }
#'
#' # fmap() modifies the contained value with a regular function.
#' mlog(2) %>>% \(x) x + 1
#' mlog(0) %>>% \(x) x + 1
#'
#' # bind() modifies the contained value with a function that returns an Either.
#' mlog(2) %>-% mlog()
#' mlog(0) %>-% mlog()
#' @name monad
NULL

#' @include pipeop.R

#' @rdname monad
#' @export
`%>>%` <- pipeop(monad::fmap)

#' @rdname monad
#' @export
`%>-%` <- pipeop(monad::bind)

#' @rdname monad
#' @export
fmap <- new_generic("fmap", "m", function(m, f, ...) S7_dispatch())

#' @rdname monad
#' @export
bind <- new_generic("bind", "m", function(m, f, ...) S7_dispatch())
method(bind, class_any) <- function(m, f, ...) join(fmap(m, f, ...))

#' @rdname monad
#' @export
join <- new_generic("join", "m", function(m) S7_dispatch())
method(join, class_any) <- function(m) bind(m, identity)

#' Functor Laws
#'
#' Classes implementing [fmap()] are expected to satisfy two functor laws:
#' preservation of identity and preservation of composition.
#'
#' The Haskell functor laws can be translated into R as follows:
#'
#' \describe{
#'  \item{Preservation of identity:}{`m %>>% identity` is equal to `m |> identity()`.}
#'  \item{Preservation of composition:}{`m %>>% (f %.% g)` is equal to `m %>>% g %>>% f`.}
#' }
#'
#' Where above `%.%` denotes function composition `\(f, g) \(x) f(g(x))`.
#'
#' @param m A functor object.
#' @param f,g Functions.
#'
#' @references <https://wiki.haskell.org/Functor#Functor_Laws>
#'
#' @family implementation laws
#' @name functor-laws
NULL

#' Monad Laws
#'
#' Classes implementing [bind()] are expected to satisfy three monad laws: left
#' identity, right identity, and associativity.
#'
#' The Haskell monad laws can be translated into R as follows:
#'
#' \describe{
#'  \item{Left identity:}{`pure(a) %>-% h` is equal to `h(a)`.}
#'  \item{Right identity:}{`m %>-% pure` is equal to `m`.}
#'  \item{Associativity:}{`(m %>-% g) %>-% h` is equal to `m %>-% \(x) g(x) %>-% h`.}
#' }
#'
#' @param pure The function to wrap a value in the monad.
#' @param h,g Monadic functions. Functions that return monadic objects.
#' @param a Any object.
#' @param m A monadic object.
#'
#' @references <https://wiki.haskell.org/Monad_laws>
#'
#' @family implementation laws
#' @name monad-laws
NULL

Try the monad package in your browser

Any scripts or data that you put into this service are public.

monad documentation built on Oct. 28, 2024, 5:07 p.m.