Haskell do notation
27-05-2015
No post anterior mostrei como implementar uma monad em Haskell e como utilizar as funções »= e return para encadear monads. Mas Haskell tem um sintax sugar chamado do notation para lidar com monads, tornando nosso código bem mais simples de ler e entender.
ghci> Just 1 >>= (\x -> Just (2 + x))
Just 3
No exemplo acima temos um exemplo de encadeamento de valores do tipo Maybe. Este é um código bem pequeno, agora, imagine um código apenas um pouco maior:
ghci> Just 1 >>= (\x -> Just (2 + x) >>= (\y -> Just (y * 3)))
Just 9
Pronto! Bastou adicionar apenas mais uma aplicação de valor e o exemplo ficou bem feio e complicado.
Podemos tentar criar um função para contornar esse problema:
calc :: Maybe Int
calc = Just 1 >>= (\x ->
Just (2 + x) >>= (\y ->
Just (y * 3)))
ghci> calc
Just 9
Não parece que melhorou muita coisa, não acha? Fazendo assim acabamos usando extensivamente lambdas. Com do notation o mesmo código fica da seguinte maneira:
calc :: Maybe Int
calc = do
x <- Just 1
y <- Just (2 + x)
Just (y * 3)
ghci> calc
Just 9
No exemplo acima os valores foram extraídos da monad para x e y, assim conseguimos usar o valor nas computações posteriores. Como do notation é apenas um sintax sugar, não precisamos de nos preocupar com os casos de falha. Vamos ver um exemplo introduzinho um caso de falha:
calc2 :: Maybe Int
calc2 = do
x <- Just 1
y <- Nothing
z <- Just (x + z)
Just (z * 3)
ghci> calc2
Nothing
Como o exemplo mostra, se introduzirmos algum valor de falha a função fail será executada. Essa função faz parte da type class Monad, mas tem uma implementação padrão:
fail :: (Monad m) => String -> m a
fail msg = error msg
Para o tipo Maybe a implementação é a seguinte:
fail _ = Nothing
Ao utilizar do notation podemos utilizar pattern matching também:
primeiraLetra :: Maybe Char
primeiraLetra = do
(x:xs) <- Just "Hercules"
return x
ghci> primeiraLetra
Just "H"
Se o pattern matching falhar, a função fail será executada:
ops :: Maybe Char
ops = do
(x:xs) <- Just ""
return x
ghci> ops
Nothing
Se você conhece Scala já deve ter utilizado for comprehensions. Em Scala for comprehensions cumprem o mesmo papel de do notation em Haskell para monads:
scala> val computation1 = for {
| x <- Some(1)
| y <- None
| z <- Some(x + y)
| } yield z
computation1: Option[Int] = None
scala> val computation2 = for {
| x <- Some(1)
| y <- Some(x + 2)
| } yield y
computation2: Option[Int] = Some(3)