Um pequeno kit funcional: Maybe, Either e uma lista encadeada, com os maps, binds, operadores e travessias para combiná-los.
import monads
O monads é escrito na própria Bern - toda função abaixo é uma função Bern comum que você pode ler em lib/monads.brn. Ele é construído sobre três tipos algébricos, que (desde a Bern 2.2) comparam com == e podem ser misturados dentro de uma lista.
adt Maybe = Just Any | Nothing adt Either = Left Any | Right Any adt List = Nil | Cons Any List
Maybe modela um valor que pode estar ausente - Just(x) ou Nothing() - então "sem resultado" é explícito em vez de um valor sentinela como -1 ou null. Either modela um de dois desfechos, por convenção Left(e) para uma falha que carrega informação e Right(x) para um sucesso. List é uma lista encadeada clássica para código que quer fazer pattern matching da estrutura diretamente; list_from / list_to fazem a ponte para as listas embutidas da Bern.
Construtores sem argumentos são escritos com parênteses vazios, tanto para construir quanto para casar: Nothing(), Nil().
Testam qual forma um Maybe tem.
is_just(Just(5)) -- Saída: true is_nothing(Nothing()) -- Saída: true
Desembrulha um Just, ou cai para padrão quando é Nothing.
from_maybe(0, Just(42)) -- Saída: 42 from_maybe(0, Nothing()) -- Saída: 0
Reduz um Maybe a um valor puro: aplica função ao conteúdo de um Just, senão devolve padrão.
maybe(-1, \x -> x * 2, Just(10)) -- Saída: 20 maybe(-1, \x -> x * 2, Nothing()) -- Saída: -1
Transforma o valor dentro de um Just; um Nothing passa intacto.
map_maybe(\x -> x + 1, Just(7)) -- Saída: Just(8) map_maybe(\x -> x + 1, Nothing()) -- Saída: Nothing()
Encadeia uma função que devolve Maybe. A cadeia curto-circuita para Nothing assim que qualquer passo produz um.
bind_maybe(Just(4), \x -> Just(x * x)) -- Saída: Just(16) bind_maybe(Nothing(), \x -> Just(x)) -- Saída: Nothing()
Testam qual lado um Either guarda.
is_right(Right(1))
-- Saída: true
is_left(Left("bad"))
-- Saída: true
Reduz um Either tratando ambos os lados, cada um com uma função.
either(\e -> "erro: " + e, \x -> "ok", Left("bad"))
-- Saída: "erro: bad"
either(\e -> 0, \x -> x, Right(9))
-- Saída: 9
Mapeia sobre o lado do sucesso (Right); um Left passa intacto.
map_either(\x -> x + 1, Right(9))
-- Saída: Right(10)
map_either(\x -> x + 1, Left("e"))
-- Saída: Left(e)
Mapeia sobre o lado da falha (Left); um Right passa intacto.
map_left(\e -> e + "!", Left("oops"))
-- Saída: Left(oops!)
Encadeia sobre o lado Right; o primeiro Left curto-circuita.
bind_either(Right(2), \x -> Right(x * 10))
-- Saída: Right(20)
bind_either(Left("stop"), \x -> Right(x))
-- Saída: Left(stop)
Desembrulham um lado, caindo para padrão no outro.
from_right(0, Right(5))
-- Saída: 5
from_right(0, Left("x"))
-- Saída: 0
Convertem entre os dois. Um Left vira Nothing e um Right vira Just; no caminho inverso, um Nothing vira Left(erro).
either_to_maybe(Right(3))
-- Saída: Just(3)
maybe_to_either("none", Nothing())
-- Saída: Left(none)
bind e mmap despacham pelo construtor, então funcionam uniformemente sobre Maybe e Either - é isso que deixa os operadores agnósticos de tipo.
Bind monádico genérico para um Maybe ou um Either.
bind(Just(5), \x -> Just(x + 1)) -- Saída: Just(6) bind(Right(5), \x -> Right(x + 1)) -- Saída: Right(6)
Map de functor genérico para um Maybe ou um Either.
mmap(\x -> x * 3, Just(4)) -- Saída: Just(12)
Forma de operador do mmap: mapeia uma função pura sobre um valor monádico.
(\x -> x + 100) <$> Just(1) -- Saída: Just(101)
Forma de operador do bind: alimenta um valor monádico para uma função monádica. Associativo à esquerda, então as cadeias se leem de cima para baixo.
Just(5) >>= \x -> Just(x + 1) >>= \y -> Just(y * 2)
-- Saída: Just(12)
Left("stop") >>= \x -> Right(x)
-- Saída: Left(stop)
Uma lista encadeada construída a partir de Cons(cabeça, cauda) e Nil(). A Bern já tem uma lista embutida rápida ([1, 2, 3]); este ADT serve para ensinar a forma e para código que faz pattern matching da estrutura. As pontes convertem de e para listas embutidas.
Convertem entre uma lista embutida e o ADT Cons/Nil.
list_to(list_from([1, 2, 3])) -- Saída: [1, 2, 3]
Aplica uma função a cada elemento.
list_to(list_map(\x -> x * x, list_from([1, 2, 3]))) -- Saída: [1, 4, 9]
Reduz pela direita; função recebe (elemento, acumulador).
list_foldr(\a, b -> a + b, 0, list_from([1, 2, 3, 4])) -- Saída: 10
list_length(list_from([4, 5, 6, 7])) -- Saída: 4
list_to(list_append(list_from([1, 2]), list_from([3, 4]))) -- Saída: [1, 2, 3, 4]
Reverte via um acumulador tail-recursivo - O(n) com prepends O(1).
list_to(list_reverse(list_from([1, 2, 3]))) -- Saída: [3, 2, 1]
Percorrem uma lista embutida, rodam um passo com efeito em cada elemento e coletam os resultados - parando no primeiro erro. Elas dependem de uma lista poder conter os dois construtores de um ADT (ex.: [Just(1), Nothing()]), o que a Bern 2.2 permite.
Transforma uma lista de Maybes num Maybe de uma lista: Just de todos os valores se cada elemento for Just, senão Nothing.
sequence_maybe([Just(1), Just(2), Just(3)]) -- Saída: Just([1, 2, 3]) sequence_maybe([Just(1), Nothing(), Just(3)]) -- Saída: Nothing()
Mapeia uma função que devolve Maybe pela lista, falhando rápido no primeiro Nothing.
traverse_maybe(\x -> Just(x + 1), [1, 2, 3]) -- Saída: Just([2, 3, 4])
As versões de Either: Right de todos os valores, ou o primeiro Left encontrado.
sequence_either([Right(1), Left("oops"), Right(3)])
-- Saída: Left(oops)
As peças se compõem em pipelines que nunca quebram com um valor ausente ou inválido. Aqui algumas entradas são parseadas, validadas e combinadas - qualquer falha colapsa a cadeia inteira para 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)
-- Saída: 10 (100 / 5 = 20, depois 20 / 2 = 10)
from_maybe(-1, Just(100) >>= \x -> safe_div(x, 0))
-- Saída: -1 (a divisão por zero curto-circuita)
E uma travessia que valida uma lista inteira de uma vez, guardando o porquê na falha com Either:
import monads
def check_positive(n) do
if (n > 0) then
return Right(n)
else
return Left("não positivo: " + n)
end
end
from_right([], traverse_either(check_positive, [3, 8, 5]))
-- Saída: [3, 8, 5]
from_left("?", traverse_either(check_positive, [3, 0, 5]))
-- Saída: "não positivo: 0"