Haskell/Verdadeiro ou falso
Predefinição:Clear Predefinição:HaskellÍndice
Igualdade e outras comparaçãoes
No capítulo usamos sinais de igualdade para definir variáveis e funções em Haskell desta forma:
r = 5
Isso significa que durante a avaliação do programa, todas as ocorrências de r são substituídas por 5 dentro do mesmo escopo. De modo similar, ao avaliar
f x = x + 3
todas os ocorrências de f seguidas de um número (o argumento de f), são substituídas por tal valor mais três.
Na Matemática, o sinal de igualdade é usado de forma diferente. Considere o seguinte problema:
Neste caso, o problema não representar como sendo ou vice-versa. Na verdade, temos uma proposição de que um número , quando somado a 3, resulta em 5. Resolver a equação significa achar qual valor de , se é que existe um, torna a proposição verdadeira. Neste exemplo simples, uma simples manipulação algébrica nos mostra que , isto é, é o número que satisfaz a proposição, pois .
Comparar valores para verificar se são iguais é algo bastante útil para a programação. Na verdade, é algum bastante elementar e necessário. Em Haskell, tais testes se parecem bastante com um equação. Entretanto, já que o sinal de igual já está sendo usado para definir alguma coisa, Haskell usa um sinal duplo de igualdade, ==, para fazer comparações. Veja:
Aqui, GHCi retorna True (verdadeiro, em inglês) porque é igual a . E se a equação não for verdadeira?
O resultado é False (falso, em inglês). Agora vamos usar a função f definimos no começo deste capítulo:
Como esperado. Já que f 2 é avaliado como sendo 2 + 3, que resulta em 5, só poderíamos esperar que 5 == 5 retornasse True.
Nós também podemos comparar dois valores numéricos para saber qual é maior. Haskell possui vários operadores para esses testes: < para menor que; >, maior que; <=, menor que ou igual a; e >=, maior que ou igual a. Esses testes funcionam exatamente como ==, igual a. Por exemplo, poderíamos usar < juntamente com a função area do capítulo anterior para saber se um círculo com um certo raio tem área menor que um outro valor.
Valores booleanos
O que acontece quando GHCi tem que determinar se essas comparações são verdadeiras ou falsas? Considere um caso diferente. Se entrarmos com uma expressão aritmética no GHCi, ela é avaliada, e o resultado numérico aparece na tela:
Se substituirmos, na comparação, o valor final da expressão aritmética, algo parecido acontece:
Enquanto o "4" retornado primeiro representa a contagem de alguma coisa, o "Verdadeiro" é um valor que representa a verdade de uma certa proposição. Tais valores são conhecidos como booleanos.[nota 1] Naturalmente, apenas dois valores são possíveis, os quais já nos foram apresentados: True e False.
Introdução a tipos de dados
True e False são valores reais, não apenas uma analogia. Em Haskell, valores booleanos tem o mesmo status que valores numéricos, e podemos manipulá-los de formas semelhantes. Um exemplo trivial:
True é, de fato, igual a True, and True não é igual a False. Agora responda: é possível dizer se 2 é igual a True?
Temos um erro no compilador. Na verdade, a pergunta original sequer faze sentido. Não se pode comparar um número com algo que não é um número, ou um booleano com algo não-booleano. Haskell possui essa noção incorporada em si, e a mensagem de erro acima, apesar de longa e um pouco intimidadora, diz exatamente isso: há um número (Num) do lado esquerdo de ==, então esperava-se um número do lado direito também; contudo, um booleano (Bool) não é um número, então o teste de igualdade falha.
Fica claro, então, que valores são separados em tipo, e que esses tipos definem os limites do que podemos ou não fazer com tais valores. True e False são valores do tipo Bool. Já o 2 é um caso um pouco mais complicado, porque existem vários tipos de números na computação, bem como na matemática (apesar de não serem equivalentes). Mas no fim, ainda é um dado Num. De forma geral, essa característica de limitação é de grande valor, porque podemos usar isso a nosso favor para controlar o comportamento dos nossos programas, criando regras que impedem o uso do programa com tipos que não fazem sentido, o que garante a funcionamento do correto do que desenvolver. Voltaremos a este tópico sobre tipos mais tarde, pois eles são uma parte importante da linguagem Haskell.
Operadores infixos
Um teste de igualdade, como 2 == 2 , também é uma expressão, bem como uma operação aritmética também o é, como 2 + 2. Ambos os casos são avaliados basicamente da mesma maneira. Quando digitamos 2 == 2 numa sessão do GHCi, ele "responde" com True, pois simplesmente avaliou a expressão. Na verdade, o operador == é uma função de dois argumentos (o lado direito e o lado esquerdo da igualdade). Só que seu uso é diferente: Haskell permite que funções de dois argumentos sejam escritas como operadores infixos, ou seja, que sejam escritas entre seus dois argumentos. Quando o nome da função é, na verdade, caracteres não-alfanuméricos (==, por exemplo), usá-las como operadores infixos é a forma mais comum. Se você deseja usá-las da forma padrão, ou seja, como operadores prefixos (com o nome da função antes dos argumentos), elas devem ser cercadas por parênteses. Veja:
É possível converter um booleano em outro também, usando negação. not é a função de negação: converte True para False, e False para True.
Haskell já possui o operador de diferença: /=, não é igual a. Entretanto, poderíamos definí-lo facilmente usando not e ==:
x /= y = not (x == y)
Note que podemos usar a notação de operadores infixos até mesmo na hora de definí-los. Além disso, um operador pode ser definido a partir de qualquer símbolo ASCII.
Outras operações booleanas
Existem outras operações com booleanos bastante úteis que necessárias: ou e e.
A operação A ou B resulta em verdadeiro se A, ou B, ou ambos forem verdadeiros. Ela é definida como sendo o operador (||) em Haskell:
A operação A e B resulta em verdadeiro se A e B forem verdadeiros. Ela é definida com osendo o operador (&&):
Guardas
Os programas escritos em Haskell geralmente usam operadores booleanos numa sintaxe bastante conveniente e abreviada. Quando uma mesma lógica é escrita com uma sintaxe diferente, chamamos isso de açúcar sintático. Quer dizer que o código se torna mais "aprazível ao paladar humano". É bom lembra-se deste termo, pois ele aparece bastante nos materiais sobre Haskell.
Um desses "açúcares", é o que chamamos de guardas, e que usa booleanos para facilitar a implementação de função de maneira bem simples. Primeiro, vamos implementar a função de valor absoluto para números reais, também chamada de função modular ou módulo. Sua definição matemática é que: se um número for positivo, seu módulo é ele mesmo; se um número for negativo, seu módulo é seu oposto, ou 0 menos ele mesmo.
Nesta função, a expressão usada para calcular depende do próprio valor de . Se for verdadeiro, então usamos a primeira expressão; se for falso, a segunda. Para expressar esse processo de decisão em Haskell, usamos guardas, sendo que a função ficaria assim:[nota 2]
absoluto x
| x < 0 = 0 - x
| otherwise = x
É interessante ver que o código acima se parece bastante com a definição matemática. Vamos por partes:
- Começamos com uma definição normal de uma função. Primeiro o nome,
absoluto, depois definindo a quantidade de argumentos que ela aceita,x.
- Em vez de usar o sinal
=e começar a escrever a definição, nós escrevemos duas alternativas de comportamento logo a baixo.[nota 3] São essas alternativas que chamamos de guardas. Deve-se lembrar que a indentação (os espaços em branco antes de|) não são opcionais, e são usados para mostrar que as guardas estão dentro do escopo da definição deabsoluto.
- Cada guarda começa com uma barra vertical,
|. Depois dela, deve-se escrever uma expressão que resulte num booleano, também chamada de condição booleana ou predicado. Ela é seguida pelo resto da definição, que começa a partir de=. A definição descrita numa guarda só será avaliada se, e somente se o predicato for avaliado comoTrue.
- O case de
otherwiseé avaliado quando nenhum dos outros casos acima for verdadeiro. Nestes caso, sexnão for menor que zero, então ele só pode ser maior que, ou igual a zero. O predicado da última guarda, portanto, poderia serx >= 0, mas você geralmente vai verotherwisecomo um caso para aceitar todas as outras condições não descritas nas guardas anteriores.
Perceba que escrevemos 0 - x em vez de -x. O que acontece é que - não é uma função de um argumento que retorna 0 - x. Na verdade, trata-se de uma abreviação sintática. Mesmo sendo bastante útil, ela geralmente causa conflito quando usada em conjunto com a função (-), que o operador da subtração. Faça um teste você mesmo no GHCi: calcule 3 menos -4, sem usar parênteses. É para evitar este tipo de problema que decidimos escrever de forma explícita 0 - x.
Guardas e where
Usar where dentro de uma função com guardas é bastante comum. Por exemplo: para saber quantas soluções reais uma equação de segundo grau possui, do tipo , temos que calcular seu discriminante, . Se ele for maior que zero, há duas raízes reais; se for nulo, há uma; se for menor que zero, nenhuma. Uma função numSolsReais em Haskell para calcular esta quantidade, poderia ser escrita assim:
numSolsReais a b c
| delta > 0 = 2
| delta == 0 = 1
| otherwise = 0
where
delta = b^2 - 4*a*c
Notas
- ↑ Este termo foi escolhido em homenagem ao matemático britânico George Boole.
- ↑ Esta função já está disponível nas bibliotecas padrões de Haskell, chamada de
abs. Não há necessidade de reimplementá-la. - ↑ Na verdade, poderíamos escrever tudo numa linha só, como
f x | caso 1 | caso 2, mas é sempre preferível quebrar linhas para facilitar a leitura.