An extensible foundation for monads in Common Lisp.
Monads provide a uniform interface to control flow for a variety of different computations.
Error handling, missing values, asynchronous computation, parsing and more can be modelled with monads.
The term monads is used somewhat loosely in the context of this
library which only requires candidate monads to implement fmap
and
flatmap
.
Salmon uses an implementation of fmap and flatmap to provide a do syntax similar to do comprehensions in Haskell and for comprehensions in Scala.
In Haskell a monad comprehension looks like this:
do
a <- [1,2,3]
let c = 5
b <- [4,5]
return (a + b + c)
The same pattern is expressed in salmon with
(mdo (a '(1 2 3))
(let (c 5))
(b '(4 5))
(yield (+ a b c)))
The mdo
macro desugars to nested applications of fmap
and
flatmap
.
Lisp’s generic methods can be implemented on data separate from the definition of that data. This allows generic methods to fulfill a similar to type classes from Haskell. The main difference is that typeclasses force the implementor to provide all methods in a contract while generic methods can be implemented one by one for any data type.
Salmon comes with builtin monad implementations for
Maybe provides a way to handle missing values. just
for when a value
is present and nil for a missing value. An mdo
comprehension
shortcircuits on nil.
(mdo (a (maybe:just 15))
(b (maybe:just 4))
(yield (+ a b)))
;; #<MAYBE:JUST 19>
Try wraps any conditions in a value to instead of having them
propagate. The try
macro can be used to handle any conditions. The
value of the expression is a failure
if a condition occurred and
otherwise it is a success
with the final value provided in the
expression (try
provides an implicit progn
).
(try:try (let ((a 10)
(b 0))
(/ a b)))
; #<TRY:FAILURE arithmetic error DIVISION-BY-ZERO signalled
; Operation was (/ 10 0).>
In an mdo
comprehension try shortcircuits on failures.
(mdo (a (try:success 15))
(b (try:success 4))
(yield (+ a b)))
;; #<TRY:SUCCESS 19>
A list
monad comprehension is shortcuited by the empty list
. The
result of a list
monad comprehension uses all combinations of values
from the unwrapped lists.
(mdo (a '(1 2 3))
(b '(4 5 6))
(yield (+ a b)))
;; (5 6 7 6 7 8 7 8 9)
A vector
monad comprehension is shortcircuited by the empty
vector
. As with lists a vector
monad comprehension uses all
combinations of values from the unwrapped vectors.
(mdo (a #(1 2 3))
(b #(4 5 6))
(yield (+ a b)))
;; #(5 6 7 6 7 8 7 8 9)
The either monad provides a mechanism for error handling holding either an error, in the left case, or a value, in the right case.
(mdo (a (right 10))
(b (right 5))
(yield (+ a b)))
;; #<RIGHT 15>
(mdo (a (right 10))
(b (left "error"))
(yield (+ a b)))
;; #<LEFT error>