Classes implementing methods for these S7 generics are called monads.
fmap()
should be implemented such that the functor
laws hold. bind()
or join()
should be implemented such that the
monad laws hold. %>>%
is the fmap()
pipe operator,
and %>-%
is the bind()
pipe operator. Operator usage is in the form m %>>% f(...)
.
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.
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)
.
See also
The monad laws and functor laws that implementations should satisfy.
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
#> $value
#> [1] 1.693147
#>
#> attr(,"class")
#> [1] "right" "either"
mlog(0) %>>% \(x) x + 1
#> $value
#> [1] "`x` must be strictly positive."
#>
#> attr(,"class")
#> [1] "left" "either"
# bind() modifies the contained value with a function that returns an Either.
mlog(2) %>-% mlog()
#> $value
#> [1] -0.3665129
#>
#> attr(,"class")
#> [1] "right" "either"
mlog(0) %>-% mlog()
#> $value
#> [1] "`x` must be strictly positive."
#>
#> attr(,"class")
#> [1] "left" "either"