Frank Coelho de Alcantara - 2021
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 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$ |
Os tipos representam conjuntos de valores com propriedades e operações similares.
$1 :: Int$ pode ser lido como um tem o tipo inteiro.
$''frank'' :: String$ pode ser lido como frank tem o tipo string.
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$ |
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.
$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.
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.
$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.
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.
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$
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$.
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...
$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)
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$
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$ |