Haskell/Casamento de padrões, if e let: diferenças entre revisões

m
<source> -> <syntaxhighlight> (phab:T237267)
mSem resumo de edição
m (<source> -> <syntaxhighlight> (phab:T237267))
 
Haskell suporta expressões de condicionais simples, da forma ''se <tt>x</tt> então <tt>p</tt> senão <tt>q</tt>''. A estrutura é a mesma que a de muitas linguagens de programação. Por exemplo, considere uma função que: retorna -1 e seu argumento for menor 0; 0 se o argumento for igual a 0; ou 1 se o argumento for maior que 0:
 
<sourcesyntaxhighlight lang="haskell">
minhaSignum x =
if x < 0
then 1
else 0
</syntaxhighlight>
</source>
 
O fato é que Haskell já possui a função <code>signum</code> que faz exatamente isso, mas vamos usá-la para exemplificar expressões condicionais.
Qualquer função que precise usar expressões condicionais também pode ser escrita usando [[Haskell/Verdadeiro ou falso#Guardas|guardas]]:
 
<sourcesyntaxhighlight lang="haskell">
minhaSignum x
| x < 0 = -1
| x > 0 = 1
| otherwise = 0
</syntaxhighlight>
</source>
 
Da mesma forma, qualquer função que use guardas também pode ser escrita com expressões ''se'' explícitas. Vejamos a função <code>absoluto</code> que definimos em [[Haskell/Verdadeiro ou falso#Guardas|Verdadeiro ou falso]]:
 
<sourcesyntaxhighlight lang="haskell">
absoluto x =
if x < 0
then -x
else x
</syntaxhighlight>
</source>
 
Mas por que há duas maneiras de se escrever expressões condicionais? Por que não usar somente <code>if</code> ou somente guardas? Apesar de serem a mesma coisa, com o mesmo desempenho computacional, sua experiência lhe dirá qual das duas formas é mais legível. Não há melhor ou pior, só mais ou menos legível.
 
Esta é uma função válida em Haskell:
<sourcesyntaxhighlight lang="haskell">
possivel x =
if x == 0
then "zero" -- then retorna um String
else "não-zero" -- else retorna um String
</syntaxhighlight>
</source>
 
Esta é uma função inválida em Haskell:
<sourcesyntaxhighlight lang="haskell">
impossivel x =
if x == 0
then 0 -- then retorna um Num
else "não-zero" -- else retorna um String
</syntaxhighlight>
</source>
 
{{Exercício|1=
Podemos escrever uma função que recebe a classificação final de um piloto (representada por um número inteiro), e que retorna quantos pontos devem ser creditados.
 
<sourcesyntaxhighlight lang="haskell">
pts :: Int -> Int
pts x =
then 1
else 0
</syntaxhighlight>
</source>
 
Por simplicidade, não vamos nos preocupar com o que aconteceria se o argumento fosse um número negativo, ou zero, por exemplo. Entretanto, numa aplicação real, deve-se sempre pensar nestes casos improváveis. Aqui simplesmente dissemos que o resultado é 0 para qualquer argumento que não seja maior que ou igual a 1 e menor que ou igual a 6.
 
Uma maneira muito mais fácil e legível seria:
<sourcesyntaxhighlight lang="haskell">
pts :: Int -> Int
pts 1 = 10
pts 6 = 1
pts _ = 0
</syntaxhighlight>
</source>
 
''Muito'' melhor. Faça o teste desta nova implementação e veja que funciona da mesma forma que o método anteiror.
A notação matemática já nos dá uma indicação do que podemos tentar: misturar casamento de padrões e guardas. De fato, Haskell nos permite fazer exatamente isso:
 
<sourcesyntaxhighlight lang="haskell">
pts :: Int -> Int
pts 1 = 10
| x <= 6 = 7 - x
| otherwise = 0
</syntaxhighlight>
</source>
 
Fica fácil perceber que agora, em vez de usarmos <code>_</code> para capturar qualquer padrão não especificado, temos <code>otherwise</code> fazendo isso.
Além de número inteiros, casamento de padrões funciona qualquer tipo de dado. Um bom exemplo é o operador lógico ''ou'', <code>(||)</code>, que vimos em [[Haskell/Verdadeiro ou falso|Verdadeiro ou falso]]. Ele poderia ser definido como sendo:
 
<sourcesyntaxhighlight lang="haskell">
(||) :: Bool -> Bool -> Bool
True || _ = True
_ || True = True
_ || _ = False
</syntaxhighlight>
</source>
 
ou
 
<sourcesyntaxhighlight lang="haskell">
(||) :: Bool -> Bool -> Bool
True || _ = True
False || y = y
</syntaxhighlight>
</source>
 
Ou ainda:
 
<sourcesyntaxhighlight lang="haskell">
(||) :: Bool -> Bool -> Bool
False || False = False
_ || _ = True
</syntaxhighlight>
</source>
 
Se usarmos casamento de padrões em funções de mais de um argumento, as expressões só serão avaliadas se ''todos'' os argumentos se encaixarem em algum padrão. E se os argumentos não se encaixarem em nenhum padrão, teremos um erro de execução. Por isso, como dito anteriormente, é sempre bom ter um caso <code>_</code> ou <code>otherwise</code> para capturar padrões inesperados ou indesejados.
Vejamos um exemplo de padrão que não funciona. A função lógica ''E'' não poderia ser definida assim:
 
<sourcesyntaxhighlight lang="haskell">
(&&) :: Bool -> Bool -> Bool
x && x = x
_ && _ = False
</syntaxhighlight>
</source>
 
Mesmo que usemos <code>x</code> nos dois argumentos para indicar que eles devem ser iguais, o compilador não compara argumentos entre si. A primeira linha é, portanto, equivalente a segunda. Além disso, ainda teremos um erro do compilador dizendo que <code>x</code> foi definido múltiplas vezes.
Os exemplos anteriores mostram que usar casamento de padrões para escrever funções resultam num código muito mais legível e elegante. Entretanto, eles não ilustram por que este método é tão importante. Primeiro, considere que você precisa implementar a função <code>fst</code>, que extrai o primeiro elemento de uma dupla. Esta parece ser uma tarefa impossível com os conhecimentos básicos de Haskell, já que a única maneira de acessar o primeiro elemento de uma dupla é usando a própria função <code>fst</code>. Entretanto, veja a função <code>fst'</code> a seguir, que realiza o mesmo trabalho que <code>fst</code>:
 
<sourcesyntaxhighlight lang="haskell">
fst' :: (a, b) -> a
fst' (x, _) = x
</syntaxhighlight>
</source>
 
Parece mágica, mas é apenas casamento de padrões com n-uplas (uma dupla, neste caso). Em <code>fst'</code>, se aplicarmos um argumento <code>(1,2)</code>, por exemplo, o primeiro elemento <code>1</code> seria associado a variável <code>x</code>, enquanto que <code>2</code> seria associado a <code>_</code> e descartado. Depois disso, pode-se fazer qualquer operação com <code>x</code> do lado direito.
Um padrão semelhante pode ser usado em listas. Vejamos a definição de <code>head</code> e <code>tail</code>
 
<sourcesyntaxhighlight lang="haskell">
head :: [a] -> a
head (x:_) = x
tail (_:xs) = xs
tail [] = error "Prelude.tail: empty list"
</syntaxhighlight>
</source>
 
A lista, assim como uma dupla, é dividia em dois elementos: cabeça e cauda. A cabeça é o elemento que aparece à esquerda do cons (<code>(:)</code>), e a cauda, o que aparece à direita. No caso de <code>head</code> e <code>tail</code>, ainda precisamos de um padrão específico para o caso de uma lista vazia, o qual usa a função <code>error</code> para exibir uma mensagem de erro durante a execução da aplicação.
Vejamos um problema simples: encontrar as soluções da equação <math>ax^2 + bx + c = 0</math>. A soluções são dadas por: <math>x = \frac {-b \pm \sqrt{b^2 - 4 a c}}{2a}</math>. Uma função para calcular a dupla de soluções de <math>x</math> poderia ser:
 
<sourcesyntaxhighlight lang="haskell">
solucao a b c =
( (-b + sqrt(b * b - 4 * a * c)) / (2 * a)
, (-b - sqrt(b * b - 4 * a * c)) / (2 * a) )
</syntaxhighlight>
</source>
 
Perceba a repetição de <code>sqrt(b * b - 4 * a * c)</code>. Poderíamos usar <code>where</code> para definir <code>raizDelta = sqrt(b * b - 4 * a * c)</code> uma única vez na função:
 
<sourcesyntaxhighlight lang="haskell">
solucao a b c =
( (-b + raizDelta) / (2 * a)
, (-b - raizDelta) / (2 * a) )
where aizDelta = sqrt(b * b - 4 * a * c)
</syntaxhighlight>
</source>
 
Ou podemos usar <code>let</code>:
 
<sourcesyntaxhighlight lang="haskell">
solucao a b c =
let raizDelta = sqrt(b * b - 4 * a * c)
in ( (-b + raizDelta) / (2 * a)
, (-b - raizDelta) / (2 * a) )
</syntaxhighlight>
</source>
 
{{aviso|Fique atento à indentação! Haskell é sensível a indentação do código, e é sempre recomendável usar espaços em branco em vez de tabulação. Em Haskell usam-se dois ou quatros espaços. Se você quiser insistir em usar tabulação, assegure-se de que todas tenham o mesmo comprimento. Falaremos mais sobre este assunto em [[Haskell/Indentação|Indentação]].}}
251

edições