Haskell/Estruturas de controle


Este módulo encontra-se em processo de tradução. A sua ajuda é bem vinda.

Como já vimos, Haskell fornece várias maneiras de expressarmos a uma escolha entre diferentes valores. Neste capítulo faremos uma revisão sobre o que já vimos e aprenderemos sobre expressões case.

if e guardas

editar

Já vimos que a sintaxe para condicionais é:

if <condição> then <se verdeiro> else <se falso>

<condição> é uma expressão que deve retornar um Bool. Se <condição> for True, então <se verdadeiro> será avaliada. Caso contrário, <se falso> será avaliada.

Diferente de outras linguagens, em Haskell, if são expressões que sempre serão avaliadas e devem sempre retornar algum valor. Em C ou Java, por exemplo, se a condição de if for falsa e não houver uma cláusula else, nenhuma ação é executada. Em Haskell, como a expressão sempre deve retornar um valor, a cláusula else é obrigatória.

Outra implicação disso é que podemos usar expressões if em qualquer lugar onde cabeira qualquer outra expressão. Por exemplo:

g x y = (if x == 0 then 1 else (sin x)/x) * y

Além disso, também vemos que aninhar expressões if é quase sempre intercambiável com guardas:

h :: Char -> String
descreverLetra c =
    if c >= 'a' && c <= 'z'
        then "Letra minúscula"
        else if c >= 'A' && c <= 'Z'
            then "Letra mainúscula"
            else "Não é uma letra ASCII"

descreverLetra' :: Char -> String
descreverLetra' c
   | c >= 'a' && c <= 'z' = "Letra minúscula"
   | c >= 'A' && c <= 'Z' = "Letra mainúscula"
   | otherwise            = "Não é uma letra ASCII"

Entretanto, guardas não são expressões. Portanto, elas não podem ser usadas e qualquer lugar. Elas também são avaliadas na ordem em que aparecem, assim com acontecem com casamento de padrões. Considere

f <padrão 1> | <predicado 1> = w
             | <predicado 2> = x

f <padrão 2> | <predicado 3> = y
             | <predicado 4> = z

Primeiramente, os padrões são casados e depois a guardas são avaliadas:

  • Se a condição de <predicado 2> for válida, ela só será avaliada se o <padrão 1> tiver sido casado.
  • Se <padrão 2> for casado e <predicado 3> e <predicado 4> forem ambos veradeiros, o resultado será y, pois <predicado 3> precede o outro.

Expressões case

editar

Uma outra estrutura de controle que ainda não havíamos citado são as expressões case.

A seguinte função f pode ser definida:

f 0 = 18
f 1 = 15
f 2 = 12
f x = 12 - x

De forma equivalente, podemos usar case:

f x =
    case x of
        0 -> 18
        1 -> 15
        2 -> 12
        _ -> 12 - x

O mesmo procedimento de casamento de padrões acontece na segunda definição: o argumento x é casado com algum dentre de case e descritos à esquerda de ->. Depois disso, a expressão do lado direito é avaliada.

Indentação é muito importante quando usamos case Cada caso deve aparecer com uma indentação mais à direita do que a linha que contém a palavra-chave of, e todos os casos devem ter a mesma indentação. Por exemplo, eis uma outra possível definição de f:

f x = case x of
  0 -> 18
  1 -> 15
  2 -> 12
  _ -> 12 - x

Também podemos vincular variáveis como num caso comum de casamento de padrões:

descreverLista :: [a] -> String
descreverLista ls =
  case ls of
    (x:xs) -> "O primeiro elemento da lista é " ++ show x ++ 
              ", e há outros " ++ show (length xs) ++ " elementos nela."
    []     -> "A lista é vazia."

Bem como condicionais if, mas diferentemente das guardas, podemos usar case em qualquer lugar onde se aceitaria uma expressão. Por exemplo:

data Cor = Preto | Branco | RGB Int Int Int

descreverCor :: Cor -> String
descreverCor c = 
  "A cor é "
  ++ case c of
       Preto           -> "preta"
       Branco          -> "branco"
       RGB 0   0   0   -> "preto"
       RGB 255 255 255 -> "branca"
       _               -> "...colorida"
  ++ ", certo?"

Como vemos, o começo da definição de descreverCor é um String: "A cor é " seguido do operador (++). Portanto, espera-se que o que vem a seguir seja também um String. Assim, as saídas de todos os casos devem ser também String, para satisfazer as restrições de tipo.

Exercício

Use case para escrever a função ifFalso que possa substituir as expressões if que já conhecemos.