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

Origem: Wikilivros, livros abertos por um mundo aberto.
[edição não verificada][edição não verificada]
Conteúdo apagado Conteúdo adicionado
mSem resumo de edição
m <source> -> <syntaxhighlight> (phab:T237267)
 
Linha 10: Linha 10:
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:
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:


<source lang="haskell">
<syntaxhighlight lang="haskell">
minhaSignum x =
minhaSignum x =
if x < 0
if x < 0
Linha 17: Linha 17:
then 1
then 1
else 0
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.
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.
Linha 35: Linha 35:
Qualquer função que precise usar expressões condicionais também pode ser escrita usando [[Haskell/Verdadeiro ou falso#Guardas|guardas]]:
Qualquer função que precise usar expressões condicionais também pode ser escrita usando [[Haskell/Verdadeiro ou falso#Guardas|guardas]]:


<source lang="haskell">
<syntaxhighlight lang="haskell">
minhaSignum x
minhaSignum x
| x < 0 = -1
| x < 0 = -1
| x > 0 = 1
| x > 0 = 1
| otherwise = 0
| 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]]:
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]]:


<source lang="haskell">
<syntaxhighlight lang="haskell">
absoluto x =
absoluto x =
if x < 0
if x < 0
then -x
then -x
else 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.
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.
Linha 60: Linha 60:


Esta é uma função válida em Haskell:
Esta é uma função válida em Haskell:
<source lang="haskell">
<syntaxhighlight lang="haskell">
possivel x =
possivel x =
if x == 0
if x == 0
then "zero" -- then retorna um String
then "zero" -- then retorna um String
else "não-zero" -- else retorna um String
else "não-zero" -- else retorna um String
</syntaxhighlight>
</source>


Esta é uma função inválida em Haskell:
Esta é uma função inválida em Haskell:
<source lang="haskell">
<syntaxhighlight lang="haskell">
impossivel x =
impossivel x =
if x == 0
if x == 0
then 0 -- then retorna um Num
then 0 -- then retorna um Num
else "não-zero" -- else retorna um String
else "não-zero" -- else retorna um String
</syntaxhighlight>
</source>


{{Exercício|1=
{{Exercício|1=
Linha 90: Linha 90:
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.
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.


<source lang="haskell">
<syntaxhighlight lang="haskell">
pts :: Int -> Int
pts :: Int -> Int
pts x =
pts x =
Linha 106: Linha 106:
then 1
then 1
else 0
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.
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.
Linha 116: Linha 116:


Uma maneira muito mais fácil e legível seria:
Uma maneira muito mais fácil e legível seria:
<source lang="haskell">
<syntaxhighlight lang="haskell">
pts :: Int -> Int
pts :: Int -> Int
pts 1 = 10
pts 1 = 10
Linha 125: Linha 125:
pts 6 = 1
pts 6 = 1
pts _ = 0
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.
''Muito'' melhor. Faça o teste desta nova implementação e veja que funciona da mesma forma que o método anteiror.
Linha 153: Linha 153:
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:
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:


<source lang="haskell">
<syntaxhighlight lang="haskell">
pts :: Int -> Int
pts :: Int -> Int
pts 1 = 10
pts 1 = 10
Linha 160: Linha 160:
| x <= 6 = 7 - x
| x <= 6 = 7 - x
| otherwise = 0
| 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.
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.
Linha 170: Linha 170:
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:
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:


<source lang="haskell">
<syntaxhighlight lang="haskell">
(||) :: Bool -> Bool -> Bool
(||) :: Bool -> Bool -> Bool
True || _ = True
True || _ = True
_ || True = True
_ || True = True
_ || _ = False
_ || _ = False
</syntaxhighlight>
</source>


ou
ou


<source lang="haskell">
<syntaxhighlight lang="haskell">
(||) :: Bool -> Bool -> Bool
(||) :: Bool -> Bool -> Bool
True || _ = True
True || _ = True
False || y = y
False || y = y
</syntaxhighlight>
</source>


Ou ainda:
Ou ainda:


<source lang="haskell">
<syntaxhighlight lang="haskell">
(||) :: Bool -> Bool -> Bool
(||) :: Bool -> Bool -> Bool
False || False = False
False || False = False
_ || _ = True
_ || _ = 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.
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.
Linha 197: Linha 197:
Vejamos um exemplo de padrão que não funciona. A função lógica ''E'' não poderia ser definida assim:
Vejamos um exemplo de padrão que não funciona. A função lógica ''E'' não poderia ser definida assim:


<source lang="haskell">
<syntaxhighlight lang="haskell">
(&&) :: Bool -> Bool -> Bool
(&&) :: Bool -> Bool -> Bool
x && x = x
x && x = x
_ && _ = False
_ && _ = 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.
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.
Linha 209: Linha 209:
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>:
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>:


<source lang="haskell">
<syntaxhighlight lang="haskell">
fst' :: (a, b) -> a
fst' :: (a, b) -> a
fst' (x, _) = x
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.
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.
Linha 221: Linha 221:
Um padrão semelhante pode ser usado em listas. Vejamos a definição de <code>head</code> e <code>tail</code>
Um padrão semelhante pode ser usado em listas. Vejamos a definição de <code>head</code> e <code>tail</code>


<source lang="haskell">
<syntaxhighlight lang="haskell">
head :: [a] -> a
head :: [a] -> a
head (x:_) = x
head (x:_) = x
Linha 229: Linha 229:
tail (_:xs) = xs
tail (_:xs) = xs
tail [] = error "Prelude.tail: empty list"
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.
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.
Linha 243: Linha 243:
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:
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:


<source lang="haskell">
<syntaxhighlight lang="haskell">
solucao a b c =
solucao a b c =
( (-b + sqrt(b * b - 4 * a * c)) / (2 * a)
( (-b + sqrt(b * b - 4 * a * c)) / (2 * a)
, (-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:
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:


<source lang="haskell">
<syntaxhighlight lang="haskell">
solucao a b c =
solucao a b c =
( (-b + raizDelta) / (2 * a)
( (-b + raizDelta) / (2 * a)
, (-b - raizDelta) / (2 * a) )
, (-b - raizDelta) / (2 * a) )
where aizDelta = sqrt(b * b - 4 * a * c)
where aizDelta = sqrt(b * b - 4 * a * c)
</syntaxhighlight>
</source>


Ou podemos usar <code>let</code>:
Ou podemos usar <code>let</code>:


<source lang="haskell">
<syntaxhighlight lang="haskell">
solucao a b c =
solucao a b c =
let raizDelta = sqrt(b * b - 4 * a * c)
let raizDelta = sqrt(b * b - 4 * a * c)
in ( (-b + raizDelta) / (2 * a)
in ( (-b + raizDelta) / (2 * a)
, (-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]].}}
{{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]].}}

Edição atual desde as 15h36min de 16 de abril de 2020


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

Este capítulo introduz expressões if, casamento de padrões e a palavra-chave let.

if / then / else[editar | editar código-fonte]

Haskell suporta expressões de condicionais simples, da forma se x então p senão q. 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:

minhaSignum x =
    if x < 0 
        then -1
        else if x > 0
            then 1
            else 0

O fato é que Haskell já possui a função signum que faz exatamente isso, mas vamos usá-la para exemplificar expressões condicionais.

Em qualquer expressão if (if em inglês significa "se"), a primeira condição e avaliada: se ela for verdadeira, a expressão dentro de then (then em inglês significa "então") é avalidada; se ela for falsa, a expressão dentro de else (else em inglês significa "senão"), que pode ou não existir, é avaliada.

No caso de minhaSignum:

  1. A condição avaliada é x < 0.
  2. Se a condição retornar um Bool True, a expressão dentro de then é avaliada: a função minhaSignum retornará -1
  3. Se a condição retornar um Bool False, a expressão dentro de else é avaliada: uma nova expressão if then else deve ser avaliada.
    1. A condição avaliada é x > 0.
    2. Se a condição retornar um Bool True, a expressão dentro de then é avaliada: a função minhaSignum retornará 1;
    3. Se a condição retornar um Bool False, a expressão dentro de else é avaliada: a função minhaSignum retornará 0.

Aqui tivemos o exemplo de duas expressões condicionais aninhadas, isto é, uma dentro de outra, o que é perfeitamente possível e bastante comum em programação.[nota 1]

Qualquer função que precise usar expressões condicionais também pode ser escrita usando guardas:

minhaSignum x
    | x < 0     = -1
    | x > 0     = 1
    | otherwise = 0

Da mesma forma, qualquer função que use guardas também pode ser escrita com expressões se explícitas. Vejamos a função absoluto que definimos em Verdadeiro ou falso:

absoluto x =
    if x < 0 
        then -x
        else x

Mas por que há duas maneiras de se escrever expressões condicionais? Por que não usar somente if 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.

Tipos numa expressão condicional[editar | editar código-fonte]

É importante observar os tipos das expressões usadas dentro de if / then / else:

  1. As condições avaliadas por if devem sempre retornar um Bool.
  2. Os valores finais das expressões dentro de then e else, podem ser de qualquer tipo, mas ambos devem ser do mesmo tipo.

Esta é uma função válida em Haskell:

possivel x =
    if x == 0
        then "zero"     -- then retorna um String
        else "não-zero" -- else retorna um String

Esta é uma função inválida em Haskell:

impossivel x =
    if x == 0
        then 0          -- then retorna um Num
        else "não-zero" -- else retorna um String
Exercício

Por que as expressões dentro de then e else contidas num mesmo if devem possuir o mesmo tipo?

Casamento de padrões: introdução[editar | editar código-fonte]

Considere um programa que analisa dados estatísticos de uma competição de corrida na qual cada piloto recebe pontos de acordo com sua posição final. As regras são:

  • 1° lugar: 10 pontos
  • 2° lugar: 6 pontos
  • 3° lugar: 4 pontos
  • 4° lugar: 3 pontos
  • 5° lugar: 2 pontos
  • 6° lugar: 1 pontos
  • Nenhum ponto para os demais.

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.

pts :: Int -> Int
pts x =
    if x == 1
        then 10
        else if x == 2
            then 6
            else if x == 3
                then 4
                else if x == 4
                    then 3
                    else if x == 5
                        then 2
                        else if x == 6
                            then 1
                            else 0

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.

Observe função que acabamos de escrever. Ela contem seis expressões ifaninhadas. Mesmo podendo copiar e colar texto, esta implementação ainda é propensa a erros, além de ser difícil de entender. Poderíamos escrever usando guardas, o que seria um pouco melhor, mas ainda seria necessário escrever todos os seis testes de igualdade. Fica como exercício.

Exercício

Reescreva a função pts usando guardas.

Uma maneira muito mais fácil e legível seria:

pts :: Int -> Int
pts 1 = 10
pts 2 = 6
pts 3 = 4
pts 4 = 3
pts 5 = 2
pts 6 = 1
pts _ = 0

Muito melhor. Faça o teste desta nova implementação e veja que funciona da mesma forma que o método anteiror.

O acontece aqui é o que chamamos de casamento de padrões. Quando a função é chamada com um certo argumento x, o compilador vai buscar qual dos padrões de argumentos mostrados do lado esquerdo são iguais a x. Quando encontrar, vai executar a expressão do lado direito:

  1. Ao executar pts 3, o comiplador passará por pts 1, pts 2 e chegará a pts 3.
  2. Como o argumento de chamada é igual ao argumento do padrão, (3 == 3), a expressão do lado direito é executada, e a função retorna 4.

É bastante comum que o último padrão use _ como argumento para indicar qualquer valor, isto é, se o padrão não se encaixar em nenhum caso anterior, então se encaixará no caso em que qualquer valor é válido:

  1. Ao executar pts 0, o comiplador passará por pts 1, pts 2, pts 3, pts 4, pts 5, pts 6 e chegará a pts _.
  2. Como _ indica qualquer valor e 0 é um valor qualquer, a expressão do lado direito é executada, e função retorna 0.

A ordem dos padrões é importante para a execução correta da função. Verifique você mesmo o que acontece se o padrão _ for definido entre 2 e 3, por exemplo. Ignore qualquer mensagem de erro que possa aparecer e tente executar pts 5.

Perceba que não usamos nenhuma varíavel na definição de pts, apenas valores. Isso é perfeitamente possível, uma vez que todos os valores retornados são constantes. Entretanto, olhando mais de perto, pts obedece a seguinte fórmula matemática:

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:

pts :: Int -> Int
pts 1 = 10
pts 2 = 6
pts x
    | x <= 6    = 7 - x
    | otherwise = 0

Fica fácil perceber que agora, em vez de usarmos _ para capturar qualquer padrão não especificado, temos otherwise fazendo isso.

Exercício

A versão de casamento de padrões de pts e esta última versão mista são ligeiramente diferentes. Você consegue ver a diferença? Conseguiria reescrever a versão mista para que as duas retornem os mesmos resultados sempre? Dica: compare a definição matemática e a implementação em Haskell e preste atenção a condição implícita da definição matemática.

Além de número inteiros, casamento de padrões funciona qualquer tipo de dado. Um bom exemplo é o operador lógico ou, (||), que vimos em Verdadeiro ou falso. Ele poderia ser definido como sendo:

(||) :: Bool -> Bool -> Bool
True || _    = True
_    || True = True
_    || _    = False

ou

(||) :: Bool -> Bool -> Bool
True  || _ = True
False || y = y

Ou ainda:

(||) :: Bool -> Bool -> Bool
False || False = False
_     || _     = True

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 _ ou otherwise 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:

(&&) :: Bool -> Bool -> Bool
x && x = x
_ && _ = False

Mesmo que usemos x 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 x foi definido múltiplas vezes.

Padrões com n-uplas e listas[editar | editar código-fonte]

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 fst, 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 fst. Entretanto, veja a função fst' a seguir, que realiza o mesmo trabalho que fst:

fst' :: (a, b) -> a
fst' (x, _) = x

Parece mágica, mas é apenas casamento de padrões com n-uplas (uma dupla, neste caso). Em fst', se aplicarmos um argumento (1,2), por exemplo, o primeiro elemento 1 seria associado a variável x, enquanto que 2 seria associado a _ e descartado. Depois disso, pode-se fazer qualquer operação com x do lado direito.

Exercício

Escreva uma função quarto que extraia o quarto elemento de uma 10-upla.

Um padrão semelhante pode ser usado em listas. Vejamos a definição de head e tail

head             :: [a] -> a
head (x:_)       =  x
head []          =  error "Prelude.head: empty list"

tail             :: [a] -> [a]
tail (_:xs)      =  xs
tail []          =  error "Prelude.tail: empty list"

A lista, assim como uma dupla, é dividia em dois elementos: cabeça e cauda. A cabeça é o elemento que aparece à esquerda do cons ((:)), e a cauda, o que aparece à direita. No caso de head e tail, ainda precisamos de um padrão específico para o caso de uma lista vazia, o qual usa a função error para exibir uma mensagem de erro durante a execução da aplicação.

Resumindo, a verdadeira vantagem de usar casamento de padrões é o fato de facilitar o acesso a valores contidos em estruturas de dados mais complexas. Casamento de padrões com listas são bastante usados em Haskell, especialmente em funções recursivas, como veremos no capítulo Recurssão.

Associações let[editar | editar código-fonte]

Para concluirmos, uma breve introdução a associações let, que nada mais são que uma alternativa ao uso de where, sendo elas intercambiáveis muitas das vezes.

A sintaxe de where é: <expressões> where <associações>. No caso de let, temos o contrário: let <associações> in <expressões>. Para se lembrar melhor, basta traduzir do inglês: seja <associações> em <expressões>.

Vejamos um problema simples: encontrar as soluções da equação . A soluções são dadas por: . Uma função para calcular a dupla de soluções de poderia ser:

solucao a b c = 
    ( (-b + sqrt(b * b - 4 * a * c)) / (2 * a)
    , (-b - sqrt(b * b - 4 * a * c)) / (2 * a) )

Perceba a repetição de sqrt(b * b - 4 * a * c). Poderíamos usar where para definir raizDelta = sqrt(b * b - 4 * a * c) uma única vez na função:

solucao a b c = 
    ( (-b + raizDelta) / (2 * a)
    , (-b - raizDelta) / (2 * a) )
        where aizDelta = sqrt(b * b - 4 * a * c)

Ou podemos usar let:

solucao a b c =
    let raizDelta = sqrt(b * b - 4 * a * c)
    in  ( (-b + raizDelta) / (2 * a)
        , (-b - raizDelta) / (2 * a) )
Observações: 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 Indentação.

Notas[editar | editar código-fonte]

  1. Se você já tem experiência com outras linguagens, já deve ter usado elseif ou algo parecido. Em Haskell, entretanto, não há esta funcionalidade, portanto precisamos aninhar as expressões de forma explícita.