Haskell

Faz algum tempo que eu vinha querendo aprender Haskell, que é uma linguagem funcional. A motivação começou com uma série de posts que andei lendo sobre C++ que faziam referência a conceitos de Haskell e dos quais eu não entendi quase nada.

Também queria relembrar alguns conceitos de linguagens funcionais, que aprendi quando vi Lisp na faculdade, mas que acabei esquecendo.


Estou estudando o livro Real World Haskell, disponível gratuitamente na internet. Achei legal o esquema deles de prover espaço para comentários em cada parágrafo do livro. Assim, os leitores atuam como revisores, sugerindo exemplos mais explicativos ou corrigindo erros de digitação.

Sendo o primeiro de uma série de anotações de meus estudos dessa linguagem, este post será como uma introdução. Nos posts seguintes pretendo discutir sobre os aspectos mais importantes da linguagem.

Antes de mais nada, é preciso instalar o interpretador de Haskell. Estou usando o ghci para rodar os programas abaixo. Para tal, deve-se executar ghci no terminal e carregar os códigos-fontes com :load <nome do arquivo>. Os códigos de exemplo podem ser salvos com a extensão .hs.

Hello World!

O fatorial é um dos primeiros problemas que vemos ao aprender recursão. Como linguagens funcionais fazem bastante uso de recursão, vamos começar com esse exemplo ao invés do famoso “Hello World!”.

-- Função que calcula o fatorial
fact n = if n == 0 
      then 1
      else n * fact (n-1)

(o wordpress não tem syntax highlight pra Haskell :( coloquei o de sql pois o jeito de comentar é o mesmo)

Aqui já aprendemos um pouco da sintaxe de uma função: o nome da função, fact (nomes de variáveis e funções devem obrigatoriamente começar com letra minúscula), o parâmetro ‘n’ logo em seguida (não é necessário colocar parênteses englobando os parâmetros e os parâmetros são separados por espaços, não por vírgulas).

Depois do sinal de igual, está definido o corpo da função, que no caso é um if. Além disso, funções em Haskell devem ser apenas uma única expressão (a presença do if dá a impressão de que há duas, mas na prática apenas uma delas será executada).

O valor retornado é o resultado da expressão executada. Além disso, o tipo retornado pelas expressões deve ser o mesmo e por isso todo if deve ser acompanhado else.

Um outro modo de escrever a função fatorial é através do seguinte código:

-- Usando casamento de padroes
fact 0 = 1
fact n = n * fact (n-1)

Aqui vemos o conceito de casamento de padrões. Lembra um pouco polimorfismo, mas aqui é um pouco diferente pois você pode definir uma constante como parâmetro. Ao chamar a função fact com o código abaixo,

fact 5

Ela vai casando com as funções até que uma bata todos os parâmetros. Assim, fact 5 só casa com fact n, pois 5 != 0. Essa função faz recursão com valores de n até que fact 0 é chamado. A ordem em que as funções estão definidas é importante, porque o casamento é feito de cima pra baixo. Note que fact 0 casa tanto com fact 0 quanto fact n e se casássemos com essa última, teriámos um loop infinito.

E se chamarmos a função com um valor real?

fact 5.1

Ela funciona, mas obviamente subtraindo 1 nunca teremos o valor 0 exato, resultando em loop infinito. Note que na definição da função não definimos tipo. Entretanto, as variáveis em Haskell têm tipo sim. A diferença é que ela faz inferência de tipos a partir dos valores quando estes não estão definidos.

Podemos restringir os tipos aceitos por uma função para que ninguém chame-a indevidamente. Para isso, podemos usar a assinatura de tipos, conforme o código abaixo:

fact :: Int -> Int 
fact 0 = 1
fact n = n * fact (n-1)

A primeira linha define que a entrada, bem como a saída sejam do tipo Int. Agora vamos calcular fatorial de 30:

> fact 30
-8764578968847253504

Deu overflow. Felizmente, Haskell tem uma implementação nativa de inteiros de precisão arbitrária (big integer), que é o tipo Integer. Podemos forçar nossa função de fatorial a usar esse tipo se formos calcular fatoriais bem grandes:

fact :: Integer -> Integer 
fact 0 = 1
fact n = n * fact (n-1)

Descobri por um acaso um modo bem mais simples de computar fatorial:

fact :: Integer -> Integer
fact n = product[1..n]

Aqui temos o conceito de listas, que são encapsuladas por colchetes, têm tamanho variável e todos os seus elementos devem ser do mesmo tipo. A sintaxe [x..y] gera uma lista com números entre x e y (inclusive) contando de 1 em 1 (y pode ser menor do que x, caso em que ele conta de trás pra frente). É possível definir um “passo” definindo o segundo elemento da sequência. Assim, [x,y..z] vai gerar de x a z, com passo (y-x). Exemplos:

[1..10] = [1,2,3,4,5,6,7,8,9,10]
[7..2]  = [7,6,5,4,3,2]
[1,4..15] = [1,4,7,10,13]

Os comentários estão fechados.

%d bloggers like this: