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
editarEncontramos 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ávelf
ao argumento casado.(x:xs)
é um padrão que casa com listas não-vazias, a quais possuem uma cabeça, que é vinculada à variávelx
, e uma cauda, vinculada axs
.
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 demap
é usada em vez da segunda. - Vincular variáveis aos valores reconhecidos. Neste caso, as variáveis
f
,x
exs
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 demap
. 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
editarMesmo 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.
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
editarUma 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
editarComo 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
editarNomeando 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
editarSuponha 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 camponome
tiver valor"João"
. - O terceiro casa com qualquer valor que use
Baz
, sendo que ele ainda vincula a variáveln
ao valor do camponumero
.
Onde aplicar
editarOnde 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
editarO 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
editarTambé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
editarPodemo 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.