A small functional toolkit: Maybe, Either and a cons-list, with the maps, binds, operators and traversals to chain them.
import monads
monads is written in Bern itself - every function below is an ordinary Bern function you could read in lib/monads.brn. It is built on three algebraic data types, which (since Bern 2.2) compare with == and may be mixed inside a list.
adt Maybe = Just Any | Nothing adt Either = Left Any | Right Any adt List = Nil | Cons Any List
Maybe models a value that may be absent - Just(x) or Nothing() - so "no result" is explicit instead of a sentinel like -1 or null. Either models one of two outcomes, by convention Left(e) for a failure that carries information and Right(x) for a success. List is a classic linked list for code that wants to pattern-match the structure directly; list_from / list_to bridge it to Bern's built-in lists.
Nullary constructors are written with empty parentheses, both to build and to match: Nothing(), Nil().
Test which shape a Maybe is.
is_just(Just(5)) -- Output: true is_nothing(Nothing()) -- Output: true
Unwrap a Just, or fall back to default when it is Nothing.
from_maybe(0, Just(42)) -- Output: 42 from_maybe(0, Nothing()) -- Output: 0
Collapse a Maybe to a plain value: apply function to the contents of a Just, otherwise return default.
maybe(-1, \x -> x * 2, Just(10)) -- Output: 20 maybe(-1, \x -> x * 2, Nothing()) -- Output: -1
Transform the value inside a Just; a Nothing passes through untouched.
map_maybe(\x -> x + 1, Just(7)) -- Output: Just(8) map_maybe(\x -> x + 1, Nothing()) -- Output: Nothing()
Chain a Maybe-returning function. The chain short-circuits to Nothing as soon as any step produces one.
bind_maybe(Just(4), \x -> Just(x * x)) -- Output: Just(16) bind_maybe(Nothing(), \x -> Just(x)) -- Output: Nothing()
Test which side an Either holds.
is_right(Right(1))
-- Output: true
is_left(Left("bad"))
-- Output: true
Collapse an Either by handling both sides with a function each.
either(\e -> "error: " + e, \x -> "ok", Left("bad"))
-- Output: "error: bad"
either(\e -> 0, \x -> x, Right(9))
-- Output: 9
Map over the success (Right) side; a Left passes through unchanged.
map_either(\x -> x + 1, Right(9))
-- Output: Right(10)
map_either(\x -> x + 1, Left("e"))
-- Output: Left(e)
Map over the failure (Left) side; a Right passes through unchanged.
map_left(\e -> e + "!", Left("oops"))
-- Output: Left(oops!)
Chain on the Right side; the first Left short-circuits.
bind_either(Right(2), \x -> Right(x * 10))
-- Output: Right(20)
bind_either(Left("stop"), \x -> Right(x))
-- Output: Left(stop)
Unwrap one side, falling back to default for the other.
from_right(0, Right(5))
-- Output: 5
from_right(0, Left("x"))
-- Output: 0
Convert between the two. A Left becomes Nothing and a Right becomes Just; going the other way, a Nothing becomes Left(error).
either_to_maybe(Right(3))
-- Output: Just(3)
maybe_to_either("none", Nothing())
-- Output: Left(none)
bind and mmap dispatch on the constructor, so they work uniformly over both Maybe and Either - which is what lets the operators stay type-agnostic.
Generic monadic bind for a Maybe or an Either.
bind(Just(5), \x -> Just(x + 1)) -- Output: Just(6) bind(Right(5), \x -> Right(x + 1)) -- Output: Right(6)
Generic functor map for a Maybe or an Either.
mmap(\x -> x * 3, Just(4)) -- Output: Just(12)
Operator form of mmap: map a plain function over a monadic value.
(\x -> x + 100) <$> Just(1) -- Output: Just(101)
Operator form of bind: feed a monadic value into a monadic function. Left-associative, so chains read top to bottom.
Just(5) >>= \x -> Just(x + 1) >>= \y -> Just(y * 2)
-- Output: Just(12)
Left("stop") >>= \x -> Right(x)
-- Output: Left(stop)
A linked list built from Cons(head, tail) and Nil(). Bern already has a fast built-in list ([1, 2, 3]); this ADT is for teaching the shape and for code that pattern-matches the structure. The bridges convert to and from built-in lists.
Convert between a built-in list and the Cons/Nil ADT.
list_to(list_from([1, 2, 3])) -- Output: [1, 2, 3]
Apply a function to every element.
list_to(list_map(\x -> x * x, list_from([1, 2, 3]))) -- Output: [1, 4, 9]
Reduce from the right; function receives (element, accumulator).
list_foldr(\a, b -> a + b, 0, list_from([1, 2, 3, 4])) -- Output: 10
list_length(list_from([4, 5, 6, 7])) -- Output: 4
list_to(list_append(list_from([1, 2]), list_from([3, 4]))) -- Output: [1, 2, 3, 4]
Reverse via a tail-recursive accumulator - O(n) with O(1) prepends.
list_to(list_reverse(list_from([1, 2, 3]))) -- Output: [3, 2, 1]
Walk a built-in list, run an effectful step on each element, and collect the results - short-circuiting on the first failure. These rely on a list being able to hold both constructors of one ADT (e.g. [Just(1), Nothing()]), which Bern 2.2 allows.
Turn a list of Maybes into a Maybe of a list: Just of all the values if every element is Just, otherwise Nothing.
sequence_maybe([Just(1), Just(2), Just(3)]) -- Output: Just([1, 2, 3]) sequence_maybe([Just(1), Nothing(), Just(3)]) -- Output: Nothing()
Map a Maybe-returning function across a list, failing fast on the first Nothing.
traverse_maybe(\x -> Just(x + 1), [1, 2, 3]) -- Output: Just([2, 3, 4])
The Either versions: Right of all the values, or the first Left encountered.
sequence_either([Right(1), Left("oops"), Right(3)])
-- Output: Left(oops)
The pieces compose into pipelines that never crash on a missing or invalid value. Here a couple of inputs are parsed, validated, and combined - any failure collapses the whole chain to Nothing:
import monads
def safe_div(a, b) do
if (b == 0) then
return Nothing()
else
return Just(a / b)
end
end
result = Just(100)
>>= \x -> safe_div(x, 5)
>>= \y -> safe_div(y, 2)
from_maybe(-1, result)
-- Output: 10 (100 / 5 = 20, then 20 / 2 = 10)
from_maybe(-1, Just(100) >>= \x -> safe_div(x, 0))
-- Output: -1 (the division by zero short-circuits)
And a traversal that validates a whole list at once, keeping the why on failure with Either:
import monads
def check_positive(n) do
if (n > 0) then
return Right(n)
else
return Left("not positive: " + n)
end
end
from_right([], traverse_either(check_positive, [3, 8, 5]))
-- Output: [3, 8, 5]
from_left("?", traverse_either(check_positive, [3, 0, 5]))
-- Output: "not positive: 0"