Anotações do Hercules

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)

Haskell do notation

Scala for expressions

comments powered by Disqus