Haskell/Casamento de padrões


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

Nos módulos passados apresentamos um pouco sobre casamento de padrões. Agora que já estamos mais familizariados com Haskell, é hora de entendermos esta ferramenta. Vamos começar com um resumo desta parte, o qual será expandido ao longo deste capítulo:

Quando usamos casamento de padrões, casamos valores com padrões e, se quisermos, vinculamos variáveis padrão casado.

Entendendo o mecanismo

editar

Encontramos casamento de padrões em todos os lugares. Considere a função map:

map _ []     = []
map f (x:xs) = f x : map f xs

Num primeiro instante, identificamos quatro padrões envolvidos, sendo dois por expressão:

  • _ é um padrão que casa com qualquer argumento, independente de seu valor e sem vincular nenhuma variável. É como um padrão coringa que ignora qualquer entrada.
  • [] é um valor e representa um padrão que casa com listas vazias. Nenhuma variável é vinculada neste caso.
  • f é um padrão que pode ser casado com qualquer argumento e vincula a variável f ao argumento casado.
  • (x:xs) é um padrão que casa com listas não-vazias, a quais possuem uma cabeça, que é vinculada à variável x, e uma cauda, vinculada a xs.

No caso (x:xs), as variáveis podem ser considerada como sub-padrões usados para casar com as duas partes de uma lista. Bem como f, x e xs casam com qualquer entrada, mas vale notar que se x for do tipo a, então xs será do tipo [a]. Isso implica que xs pode ser vinculada a uma lista vazia [] cujo tipo é [a] e, portanto, o (x:xs) pode ser casado com listas contendo um único elemento.

Então, podemos dizer que casamento de padrões nos permite:

  • Reconhecer valores. Quando usamos map e o segundo argumento é casado com [], a primeira definição de map é usada em vez da segunda.
  • Vincular variáveis aos valores reconhecidos. Neste caso, as variáveis f, x e xs são vinculadas quando a segunda definição é usada e podemos, então, usar estes nomes do lado direito da expressão para nos referirmos aos argumentos de map. Mas como podemos ver na primeira definição, onde usamos [] e _, vincular variáveis não é essencial.
  • Dividir valores em partes menores. É exatamente isso que (x:xs) faz, pois vincula nomes às duas partes (cabeça e cauda) de uma lista não-vazia.

Envolvendo construtores de tipo

editar

Mesmo com a análise mais detalhada que acabamos de fazer, ainda parece um passe de mágica o fato de podermos desconstruir uma lista que foi unida usando a função (:). Talvez você queira tentar fazer algo similar com uma lista unida por (++):

eliminarTres ([x,y,z] ++ xs) = xs

Você pode testar no GHCi, mas ele retornará um erro. A função (++) não pode ser usada em casamento de padrões. Na verdade, a maioria das funções que não podem ser usadas.

Mas então, quais funções podem ser usadas em casmento de padrões? A resposta é simples: construtores de tipos. Por exemplo, considere o tipo:

data Foo = Bar | Baz Int

Temos que Bar e Baz são construtores do tipo Foo. Sendo assim, podemos usá-los em padrões de argumentos que devem ser do tipo Foo, por exemplo:

f :: Foo -> Int
f Bar     = 1
f (Baz x) = x - 1

Relembrando o capítulo passado, é exatamente assim que mostrarData e mostrarAniversario funcionam:

data = Data = Data Int Int Int -- ano, mês, dia

mostarData :: Data -> String
mostrarData (Data a m d) = show a ++ "-" ++ show m ++ "-" ++ show d

O argumento (Data a m d) casa com um tipo de dado Data, que é criado usando o construtor Data, e vincula as variáveis a, m e d aos valores contidos em Data.

Por que a função (:) funciona em listas?

editar

Isso acontece porque (:) é um operador de construção, assim como Data, Bar e Baz. Listas são definidas usando data:

data [a] = [] | a : [a]

Assim, uma lista vazia [], além de ser um valor por si só, também é um construtor de tipo. A função (:) também é construtora de tipo. Deste modo, a restrição de que apenas funções construtoras de tipo podem ser usadas em casamento de padrões é satisfeita.

A forma correta de implementar eliminarTres é convertendo a lista para a notação de cons:

eliminarTres (_:_:_:xs) = xs
eliminarTres _          = []

O padrão (_:_:_:xs) casa com qualquer lista de pelo menos três elementos. O segundo retorna uma lista vazia caso a entrada tenha dois elementos ou menos.

Nota: Mesmo sendo possível criar a função eliminarTres do zero, trata-se de um desperdício de esforço. Como já foi dito em outras partes deste texto, é altamente recomendável reusarmos funções já definidas no Prelude ou outras bibliotecas. Assim sendo, seria melhor escrever eliminarTres = drop 3.

Construtores de n-uplas

editar

Uma analogia semelhante é válida para n-uplas. O construtor deste tipo é o operador vírgula: para duplas, usa-se uma vírgula, (,); para triplas, duas, (,,); e assim por diante. Por serem construtores, podemos usar casamento de padrões:

fstMaisSnd :: (Num a) => (a, a) -> a
fstMaisSnd (x, y) = x + y

norma3D :: (Floating a) => (a, a, a) -> a
norma3D (x, y, z) = sqrt (x^2 + y^2 + x^2)

Bem como cons, podemos usar (,) como uma função, o que pode ser útil em algumas ocasiões:

Prelude> (,) 5 3
(5,3)
Prelude> (,,,) "George" "John" "Paul" "Ringo"
("George","John","Paul","Ringo")

Entretanto, o uso de parênteses é obrigatório no modo infixo: 5, 3 é inválido, enquanto (5, 3) é válido.

Casando valores literais

editar

Como já vimos antes, podemos casar padrões com valores literais. Por exemplo:

f :: Int -> Int
f 0 = 1
f 1 = 5
f 2 = 2
f _ = -1

Neste caso, a definição de f esta casando seus argumentos com valores pré-definidos (0, 1 e 2), mais um caso geral (ou de exceção) (_). Em geral, valores específicos podem ser casados individualmente bem como juntos a construtores. Veja:

g :: [Int] -> Bool
g (0:[]) = False
g (0:xs) = True
g _ = False

A função g retorna False se a entrada for uma lista de um único elemento que seja igual a 0; True se a lista tiver mais que um elemento e a cabeça for igual 0; e False para todos os outros casos. Listas com tamanhos fixos também podem ser usadas para definir um padrão, como [a,b,c], [1,2,3] ou "abc".

Devemos saber, entretanto, que o uso de nomes de variáveis tem contexto limitado. Por exemplo, não podemos definir uma variável fora da função e usá-la no casamento de padrões:

k = 1

h :: Int -> Bool
h k = True
h _ = False

O compilador pode aceitar a definição, mas ele não casará o argumento k com a variável global k. Podemos, entretanto, comparar os dois valores dentro da função, desde que eles possuam nomes diferentes:

k = 1

h :: Int -> Bool
h x = (x == k)
Exercícios
Teste a primeira versão de h no GHCi. O que acontece? Dê um exemplo em que ela retorne False.

Mais sintaxe

editar

Nomeando padrões com @

editar

Em certos momentos, quando casamos algum sub-padrão, talvez ainda seja útil vincular um nome para o argumento todo. A sintaxe @ nos permite fazer justamente isso: usando var@padrao, definimos o nome var para nos referimos ao argumento de entrada e padrao para definir o padrão de casamento.

Por exemplo:

primeiroEsomar :: [Int] -> String
primeiroEsomar lista@[] =
    "A lista é vazia e sua soma é " ++ show (sum lista)
primeiroEsomar lista@(x:xs) =
    "O primeiro elemento é " ++ (show x) ++ " e a soma da lista é " ++ show (sum lista)

Aqui usamos lista para nos referirmos ao argumento de entrada de primeiroEsomar e passá-lo para a função sum. No segundo padrão, além disso, temos x casado com a cabeça de lista, e xs com sua cauda.

Casamento com registros

editar

Suponha o seguinte tipo definido usando a sintaxe de registros:

data Foo = Bar | Baz {numero :: Int, nome :: String}

Podemos casar padrões com registros usando apenas os construtores (Bar ou Baz), ou também os campos:

f :: Foo -> String
f Bar    = "Temos um Bar"
f Baz {} = "Temos um Baz"

g :: Foo -> Int
g Bar                 = 0
g Baz {nome = "João"} = 100
g Baz {numero = n}    = n * 2

Em f, comparamos apenas os construtores. Para casar apenas os construtores de um registro, deve-se acrescenter um par de chaves à esquerda, como em Baz {}. Já em g, os padrões envolvem construres e valores específicos:

  • No primeiro caso usamos apenas o construtor Bar.
  • O segundo só é casado a entrada usar o construtor Baz e se o campo nome tiver valor "João".
  • O terceiro casa com qualquer valor que use Baz, sendo que ele ainda vincula a variável n ao valor do campo numero.

Onde aplicar

editar

Onde podemos usar casamento de padrões? A resposta curta é onde quer que possamos vincular variáveis, podemos usar casamento de padrões. Com base no que já sabemos até agora de Haskell, vejamos onde podemos aplicar casamento de padrões.

Definição de funções

editar

O uso mais óbvio é do lado esquerdo da definição de uma função, e foi o que vimos até o momento. Para exemplos, basta rever este capítulo.

Expressões let e cláusulas where

editar

Tanto let quando where são métodos distintos para vincular variáveis de forma local, sem envoler os argumentos de entrada de uma função, por exemplo. Veja:

y = let (x:_) = map (*2) [1..3]
    in x + 5

y' = x + 5
    where (x:_) = map (*2) [1..3]

Tanto y quando y' retornam o mesmo resultado. Em ambos os casos vinculamos a variável x ao primeiro elemento de map (*2) [1..3].

Expressões lambdas

editar

Também podemos usar casamento de padrões em funções anônimas:

permutar = map (\(x,y) -> (y,x))

É claro, entretanto, que a sintaxe das expressões lambdas permitem apenas um padrão por argumento da expressão.

Compressão de listas

editar

Podemo usar casamento de padrões depois de | numa compressão de lista. Na verdade, é um caso bastante útil para algumas expressões complexas, mas vejamos com simples n-uplas:

Prelude> xs = [(x, y, x * y) | x <- [1..5], y <- [9,17]]
Prelude> [a + c | (a, _, c) <- xs]
[10,18,20,36,30,54,40,72,50,90]

Blocos do

editar

Dentro de blocos do como os que usamos em Entradas e saídas simples, podemos usar casamento de padrões do lado esquerdo de vinculações <-:

putChar = do
    (c:_) <- getLine
    putStrLn [c]

Além disso, ainda podemos usar casamento de padrões dentro expressões let situadas dentro de do da mesma forma que expressões let comuns.