Saltar para o conteúdo

Haskell/Verdadeiro ou falso

Origem: Wikilivros, livros abertos por um mundo aberto.


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

Igualdade e outras comparaçãoes

[editar | editar código-fonte]

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 booleanos

[editar | editar código-fonte]

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 dados

[editar | editar código-fonte]

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 infixos

[editar | editar código-fonte]

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 booleanas

[editar | editar código-fonte]

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

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 where

[editar | editar código-fonte]

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