Introdução ao
Haskell

Frank Coelho de Alcantara - 2021    

História

Se chama Haskell em homenagem Haskell Curry.

A primeira versão foi lançada em 1990, apesar de estar em trabalho desde 1987.

"Linguagem de uso geral, estaticamente tipada, puramente funcional, com inferência de tipos, e avaliação preguiçosa".

Quase um sonho, do ponto de vista das linguagens de programação, criado sobre valores, funções e tipos.

Valores e Funções

Valores são termos. Por exemplo: $10$ é um valor inteiro; $''frank''$ é uma coleção de caracteres e $3.1415$ é um número em ponto flutuante. Os valores são processados pelas funções. A função $+$ usa dois valores numéricos e devolve uma valor numérico enquanto a função $++$ toma duas coleções de caracteres (strings) e devolve uma string.

Exatamente como na matemática, uma função mapeia um valor de entrada em um valor de saída. As funções podem ser combinadas por exemplo; $lenght ("frank" ++ " de Alcantara")$.

Matemática Haskell
$f_{(x)}$ $f \space x$
$f_{(x,y)}$ $f \space x \space y$
$f_{g_{(x)}}$ $f \space ( g \space x)$
$f_{(x)}g_{(x)}$ $f \space x \times g \space x$

Tipos

Os tipos representam conjuntos de valores com propriedades e operações similares.

  • Int = $[1, 2, 3, ...]$.
  • Float = números em ponto flutuante de precisão simples.
  • Double = números em ponto flutuante de precisão dupla.
  • Char = caracteres incluíndo todo o range Unicode.
  • Strings = sequências de caracteres, listas.
  • Bool = verdadeiro (true) ou falso (False).

$1 :: Int$ pode ser lido como um tem o tipo inteiro.

$''frank'' :: String$ pode ser lido como frank tem o tipo string.

Funções

Uma função é composta de duas partes: head e body. Na head temos o nome da função e de seus argumentos: $incrementa \space \space x$ e no body, ou corpo, temos a expressão do mapeamento.

Por exemplo: $incrementa \space \space x = x+1$ é uma função que toma um valor x e devolve este valor acrescido de 1.

Uma boa prática de programação e escrever uma declaração de tipo para a função. A esta declaração damos o nome de assinatura da função. Esta assinatura deve ser declarada antes da função em si. Logo:

$incrementa :: Int \rightarrow Int$
$incrementa \space \space x = x+1$

Identificadores

Os nomes devem começar com uma letra minúscula, ou com o sublinhado $\_$.

Os nomes só podem conter o sublinhado $\_$, letras, números ou o apostrofo $'$.

No caso das funções usamos, tradicionalmente, Camell Case separando os signos por significado. Por exemplo: $aplicarLogNaLista$ ou $toUpper$. São dois nomes de funções que explicitam o mapeamento que será realizado de forma clara.

Avaliação

$incrementa :: Int \rightarrow Int$
$incrementa \space \space x = x+1$
$print (incrementa \space 10)$

Ao final da execução, ou avaliação da expressão, da última instrução, será apresentado o valor $11$:

$incrementa \space \space 10 \Rightarrow (10 + 1) \Rightarrow 11$

A seta dupla a direita está mostrando o processo de avaliação, passo a passo.

Assinaturas de Tipos

Funções mapeiam valores de entrada em valores de saída.

$incrementa :: Int \rightarrow Int$ -- assinatura
$incrementa \space \space x = x+1$ -- declaração

Estas assinaturas, são opcionais, mas permitem a identificação de erros e documentam a sua intenção quando criou a função.

Podemos usar comentários para melhorar a qualidade da documentação do código: -- para comentários na mesma linha e {- e -} para comentários com muitas linhas.

Argumentos múltiplos

$media :: Float \rightarrow Float \rightarrow Float$ -- assinatura
$media \space \space a \space b = (a+b)/2.0$ -- declaração

Neste caso, temos uma função com dois argumentos em ponto flutuante que devolve um valor em ponto flutuante.

$media \space \space 4.0 \space \space 5.0 \Rightarrow (4.0+5.0)/2.0 \Rightarrow 4.5$

Existe um trabalho a ser feito sobre este tema.

Variáveis

Identificadores são imutáveis em Haskell. Isso quer dizer que não existem variáveis.

$x =5$
$x = 6$

A existência destas duas declarações irá provocar um erro. Esta imutabilidade também impede a criação de laços de repetição.

Usaremos muita recursão em Haskell.

Infix e Prefix

Vimos no Cálculo Lambda a notação prefix, mas na nossa função $media$ usamos infix:

$media :: Float \rightarrow Float \rightarrow Float$ -- assinatura
$media \space \space a \space b = (a+b)/2.0$ -- declaração

Poderíamos ter usado prefix:

$media :: Float \rightarrow Float \rightarrow Float$ -- assinatura
$media \space \space a \space b = ((+) \space a \space b))/2.0$ -- declaração

Podemos usar a função $media$ de duas formas:

$y = media \space \space 4.5 \space \space 5.4 $
$y = 4.5 \space \space 'media' \space \space 5.4$

A assinatura de tipos de operadores só pode ser feita em infix: $(+) :: \space Int \rightarrow Int \rightarrow Int$

Overloading

Poderíamos definir uma assinatura do operador $(+)$ para cada tipo de número.

O Haskell usa um conceito mais avançado: Classes de tipos.

Por exemplo temos a classe $Num$ que engloba todos os tipos numéricos da linguagem.

Neste caso, podemos dizer que $a \in Num$ que em haskell fica abreviado por $Num \space \space a$.

Então teremos: $(+):: \space \space Num \space \space a \Rightarrow a \rightarrow a \rightarrow a$

Outras duas classes muito importantes são $Eq$ e $Show$.

Eq e Show

A classe $Eq$ contém todos os tipos para os quais o operador $==$ está definido. Este é o operador usado em Haskell para verificar a igualdade lógica. $(==) :: \space \space Eq \Rightarrow a \rightarrow a \rightarrow Bool$

$ 2 == 2 \Rightarrow true$
$4.5 == 5.4 \Rightarrow False$
$"frank \space de \space alcantara" == ("frank" ++ \Rightarrow " de alcantara") \Rightarrow true$

A classe $Show$ contém todos os tipos que o sistema sabe como converter para string. $show :: Show \Rightarrow a \rightarrow String$

$show \space 45 \space \space \Rightarrow \space \space ''45''$
$show \space 1.2 \space \space \Rightarrow \space \space ''1.2''$

Se já não existir uma forma de converter para string será indicado o erro No instance for Show...

Classes Frequentes

$Show:$ $show :: Show \space a \Rightarrow a \rightarrow String$
$Eq$: $(==), (/=):: Eq \space \space a \Rightarrow a \rightarrow a \rightarrow Bool$
$Ord$: $(\gt ), (\lt), (>=), (<=):: Ord \space \space a \Rightarrow a \rightarrow a \rightarrow Bool$
$Num$: $(+), (-), (*) :: Num \space \space a \Rightarrow a \rightarrow a \rightarrow a$
$Integral$: $div, mod :: Integral \space \space a \Rightarrow a \rightarrow a \rightarrow a$
$Fractional$: $(/) :: Fractional \space \space a \Rightarrow a \rightarrow a \rightarrow a$
$Floating$: $sin, cos, tan, exp, sqrt, ... :: Floating \space \space a \Rightarrow a \rightarrow a$

Para ver todas as classes de tipos, no GHCI, digite: :info NomeDaClasse (:info Num)

Branches (Decisão)

Suponha que você queira implementar uma função $maximo$ de devolve o maior entre dois valores.

$maximo :: Ord \space \space a \Rightarrow a \rightarrow a \rightarrow a$ -- assinatura
$maximo \space \space x \space \space y = if \space \space x >= y \space \space then \space \space x \space \space else \space \space y$ -- declaração
$maximo \space \space 4 \space \space 5 \Rightarrow 5$ -- uso
$maximo \space \space 5 \space \space 4 \Rightarrow 5$ -- uso

A comparação é feita com operadores que pertencem a classe $Ord$. Logo, faz sentido usar esta classe.

O fator condicional $x >= y$ é avaliado primeiro, depois o $if$

Guardas (Decisão)

Podemos definir branches usando guardas.

$maximo :: Ord \space \space a \Rightarrow a \rightarrow a \rightarrow a$ -- assinatura
$maximo \space \space x \space \space y$
$\space \space \space \space | x >= y = x$
$\space \space \space \space |x < y \space \space = y$

Ou ainda:

$maximo :: Ord \space \space a \Rightarrow a \rightarrow a \rightarrow a$ --assinatura
$maximo \space \space x$
$\space \space \space \space | x>= y \space \space= x$
$\space \space \space \space |otherwise \space \space =y$