Haskell/Verdadeiro ou falso


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

Igualdade e outras comparaçãoesEditar

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:

Exercícios
Resolva 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:

Prelude> 2 + 3 == 5
True

Aqui, GHCi retorna True (verdadeiro, em inglês) porque   é igual a  . E se a equação não for verdadeira?

Prelude> 7 + 3 == 5
False

O resultado é False (falso, em inglês). Agora vamos usar a função f definimos no começo deste capítulo:

Prelude> let f x = x + 3
Prelude> f 2 == 5
True

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.

Prelude> let area r = pi * r ^ 2
Prelude> area 5 < 50
False

Valores booleanosEditar

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:

Prelude> 2 + 2
4

Se substituirmos, na comparação, o valor final da expressão aritmética, algo parecido acontece:

Prelude> 2 == 2
True

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 dadosEditar

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:

Prelude> True == True
True
Prelude> True == False
False

True é, de fato, igual a True, and True não é igual a False. Agora responda: é possível dizer se 2 é igual a True?

Prelude> 2 == True

<interactive>:1:0:
    No instance for (Num Bool)
      arising from the literal ‘2’ at <interactive>:1:0
    Possible fix: add an instance declaration for (Num Bool)
    In the first argument of ‘(==)’, namely ‘2’
    In the expression: 2 == True
    In an equation for ‘it’: it = 2 == 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 infixosEditar

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:

Prelude> 4 + 9 == 13
True
Prelude> (==) (4 + 9) 13
True

É 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.

Prelude> not (5 * 2 == 10)
False

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 booleanasEditar

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:

Prelude> True || True
True
Prelude> True || False
True
Prelude> False || True
True
Prelude> False || False
False

A operação A e B resulta em verdadeiro se A e B forem verdadeiros. Ela é definida com osendo o operador (&&):

Prelude> True && True
True
Prelude> True && False
False
Prelude> False && True
False
Prelude> False && False
False

GuardasEditar

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 de absoluto.
  • 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 como True.
  • O case de otherwise é avaliado quando nenhum dos outros casos acima for verdadeiro. Nestes caso, se x não for menor que zero, então ele só pode ser maior que, ou igual a zero. O predicado da última guarda, portanto, poderia ser x >= 0, mas você geralmente vai ver otherwise como um caso para aceitar todas as outras condições não descritas nas guardas anteriores.
Nota: Não há nada de mágico por trás de otherwise. Na verdade, ele é definido simplesmente como sendo otherwise = True. Isso quer dizer que sempre que otherwise for avaliado, sempre retornará True e, portanto, a definição descrita em sua guarda será executada.

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 whereEditar

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

NotasEditar

  1. Este termo foi escolhido em homenagem ao matemático britânico George Boole.
  2. Esta função já está disponível nas bibliotecas padrões de Haskell, chamada de abs. Não há necessidade de reimplementá-la.
  3. 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.