• Revista PROGRAMAR: Já está disponível a edição #53 da revista programar. Faz já o download aqui!

Rui Carlos

Tutorial de Haskell

30 mensagens neste tópico

Introdução

O Haskell é uma linguagem de programação funcional, baseada no lambda-calculus, que surgiu nos anos 80. Esta linguagem possui algumas das mais recentes caracteristicas das linguagens funcionais como lazy evaluation, pattern matching e modularidade.

Trata-se de uma linguagem pouco eficiente e como tal ainda não muito usado em empresas. Apesar de tudo é uma das linguagem mais populares no meio académico e muito usada em investigação.

Os programas tanto podem ser compilados (usando um compilador como o GHC) ou interpretados (usando o GHCi ou o HUGS, por exemplo). Para fazer o download basta irem a http://www.haskell.org/haskellwiki/Implementations

Ao longo deste texto vou utilizar como compilador/interpretador o GHC.

Algumas noções preliminares

Um programa em Haskell deve começar com a linha module Nome_Mod where, onde 'Nome_Mod' é o nome deste módulo, que tem que começar por letra maiúscula. O nome do ficheiro deverá ser 'Nome_Mod.hs', embora não seja obrigatório, será necessário caso queiramos importar este módulo noutro programa.

Para definir um comentário de uma linha usamos os caracteres '--' e para comentários de mais do que uma linhas usamos '{-' (e '-}').

Os tipos em Haskell começam por letras maiúsculas sendo os básicos Int, Integer, Float, Double, Char e Bool.

O primeiro programa

module PrimeiroProg where

funcao :: Int -> Int
funcao x = x^2

Talvez estivessem à espera que colocasse aqui o famoso "Hello World", no entanto definir uma função que imprime uma mensagem no ecrã é um pouco complexo (obriga-nos a utilizar monad's), como tal fica um exemplo da função 'f(x)=x2'

Para testar o programa basta guardar o ficheiro ('PrimeiroProg.hs') e, depois de instalado o GHC, executar o comando 'ghci PrimeiroProg.hs' (em Windows normalmente até podemos clicar duas vezes sobre o icon que o GHC abre-se automaticamente).

Para testar a função fazem funcao n onde n é um inteiro qualquer e deverão obter como resultado n2.

De referir que a 3ª linha (a assinatura da função) não é obrigatória, o Haskell possui um mecanismo de inferência de tipos que lhe permite determinar o tipo das funções.

Como podem ver é muito simples definir funções matemáticas em Haskell. Deixo-vos mais alguns exemplos:

f1 :: Float -> Float -> Bool
f1 x y = x * y > 0

divInt :: Int -> Int -> Int
divInt a b = if b == 0 then a else div a b

resto :: Int -> Int -> Int
resto a b = if b /= 0 then a `mod` b else 0

soma x y = (+) x y

-- verifica se um caracter é uma vogal
vogal :: Char -> Bool
vogal a = if a=='a' || a=='e' || a=='i' || a=='o' || a=='u'
          then True
          else False

f2 :: (Float,Float,Float) -> Float -> Bool
f2 (a,b,c) d = sin ((a * b) / c) > d

Aqui podemos ver exemplo de funções com if's (que ao contrário de muitas linguagens, precisam sempre do 'else') e os operadores prefixos div e mod. Na função resto podemos ver como "transformar" um operador prefixo (neste caso o mod) num infixo, basta envolve-lo por acentos graves (e não plicas). Também operadores infixos podem passar a prefixo envolvendo-os com parentesis (tal como acontece na função soma).

Reparem que na função vogal o then passa para a linha seguinte, podemos fazê-lo desde de que a linha esteja identada (bastava ser um caracter).

Por último podemos ver a utilização de tuplos. Neste exemplo todos os elementos são do tipo Float, mas podiam ser de tipos diferentes.

Para simplificar as coisas, podemos considerar que uma função do tipo Int->Int->Int é uma função que recebe dois inteiros e devolve um inteiro. No entanto isso não é verdade, mas isto são pormenores um pouco mais avançados da linguagem.

Podem agora testar as funções fazendo, por exemplo, 'resto 14 3', 'vogal 'b'', 'f2 (1,2,3) 0'...

Recursividade

Em Haskel não há ciclos, como tal a maior parte dos problemas resolvem-se com recursividade.

factorial n = if n == 0 then 1 else n * factorial (n-1)

quadrado n = if n == 0 then 0 else quadrado (n-1) + 2 * n - 1

Mais à frente serão apresentados exemplos mais complexos...

Outras formas de exprimir condicionais

As seguintes funções são todas equivalentes. Tratam-se de funções que verificam se um valor é 0 ou não.

isZero a = if a == 0 then True else False

isZero a | a == 0 = True
       | a /= 0 = False

isZero a | a == 0 = True
       | otherwise = False

isZero a = case a of
           0 -> True
           x -> False

isZero a = case a of
           0 -> True
           _ -> False

isZero 0 = True
isZero _ = False

Acho que é relativamente fácil perceber os exemplos. Talvez os mais complicados sejam os 3 últimos onde é usado pattern matching.

Basicamente aqui o programa tenta encaixar os argumento em padrões (este caso verificar se coincide com 0). O caracter '_' significa "qualquer coisa", ou seja, tudo encaixa no '_'.

De referir ainda que no último exemplo não podiamos trocar a ordem das linhas, caso contrário daria sempre False, pois o programa executa a primeira "versão" que encaixe no padrão.

Nestes exemplos apenas temos dois casos, mas se fossem mais o código seria semelhante.

Listas e strings

O Haskell suporta também um tipo de dados muito útil, as listas. Ao contrário dos tuplos, os elementos de uma lista têm que ser todos do mesmo tipo. Vejamos alguns exemplos.

comprimento l=length l

vazia [] = True
vazia _  = False

primeiro :: [int] -> Int
primeiro lista = head lista

cauda [] = []
cauda lista = tail lista

junta :: [int] -> [int] -> [int]
junta lista1 lista2 = lista1 ++ lista2

adiciona :: Int -> [int] -> [int]
adiciona x l = x : l

somatorio [] = 0
somatorio (x : xs) = x + (somatorio xs)

Aqui foram utilizadas as função head (devolve o primeiro elemento da lista), tail (devolve a "cauda" da lista), '++' (junta duas listas) e ':' (adiciona um elemento no início).

A última função calcula o somatório de uma lista de números. Quando recebe uma lista vazia ('[]') devolve 0, se não for vazia, então a lista encaixa no padrão x : xs, ou seja, é um primeiro elemento (x) mais uma cauda (xs), cauda essa que pode ser uma lista vazia.

As strings em Haskell não são mais do que listas de caracteres.

juntaLetra :: Char -> String -> [Char]
juntaLetra l str = l : str

juntaNoFin l str = str ++ [l]

-- a função 'take' selecciona os n primeiro elementos de uma lista
dezLetras str = take 10 str

comprimento str = length str

De seguida mostram-se algumas formas de definir listas

-- lista dos inteiros de 1 a 100
[1..100]

-- lista dos números pares maiores do que zero e menores do que 100
[2,4..100]

-- lista dos quadrados dos numeros inteiros de 1 a 20
[n^2 | n <- [1..20]]

-- lista [(1,0),(2,1),(3,0),...]
[(n,ispar) | n <- [1..20] , ispar <- [0,1] , ((mod n 2==0 && ispar==1) || (odd n && ispar==0))]

-- lista de tadas as combinações de elementos de [1,2,3] e de [4,5,6]
[(x,y) | x <- [1,2,3] , y <- [4,5,6]]

-- lista de TODOS os números inteiros maiores do que 0
[1..]

Um pormenor interessante, derivado do facto do Haskell ser lazy, e que alguns de vocês devem ter achado estranho, é que podemos ter listas infinitas, como no último exemplo. Quando tiverem a consola do GHCi aberta digitem [1..] e verão o interpretador a tentar mostrar todos os número (até que você digitem 'Ctrl+C').

Existem algumas funções interessantes sobre listas, como por exemplo map, filter e foldl. Ficam aqui alguns exemplos.

vezes2 n = n * 2

-- aplica a função 'vezes2' a todos o elementos de uma lista
duplica :: [int] -> [int]
duplica l = map vezes2 l

-- ou alternativamente
duplica l = map (* 2) l

-- filtra todos os elementos diferentes de 0 (usa a função 'isZero' definida anteriormente)
naoZeros l = filter (not . isZero) l

Podemos ver também a aplicação da composição de funções ('.'): (not . isZero) n é equivalente a not (isZero n).

Sinónimos de tipos e novos tipos de dados

Em Haskell podemos definir sinónimos de tipos. Por exemplo, podemos associar ao nome Par o tipo (Int,Int). Para tal usamos a declaração type.

type Par=(Int,Int)

Desta forma escrever Par ou (Int,Int) é a mesma coisa. No próximo exemplo as funções f1 e f2 são exactamente a mesma coisa.

f1 :: (Int,Int) -> Int
f1 (x,y) = x + y

f2 :: Par -> Int
f2 (x,y) = x + y

Como já foi referido, strings em Haskell são lista de caracteres, ou seja, foram declaradas da seguinte forma: type String=[Char].

Mas podemos fazer muito mais do que definir sinónimos em Haskell. Podemos definir novos tipos através da declaração data.

Por exemplo, quando fazemos a divisão de dois números o denominador não pode ser zero, caso isso acontença é um erro. Então vamos definir um tipo de dados que permite representar um inteiro e situações de erro e algumas funções que usam esse tipo.

data ResDiv = Erro | Res Int

divisao :: Int -> Int -> ResDiv
divisao _ 0 = Erro
divisao n d = Res (div n d)

mult :: ResDiv -> ResDiv -> ResDiv
mult Erro    _       = Erro
mult _       Erro    = Erro
mult (Res x) (Res y) = x * y

-- converte um valor do tipo ResDiv para Int.
-- o valor erro é considerado 0
res2int :: ResDiv -> Int
res2int Erro    = 0
res2int (Res x) = x

Neste caso o tipo ResDivservia quando estivesse-mos a trabalhar com inteiros. Mas podemos generalizá-lo para qualquer tipo de dados.

data ResDiv a = Erro | Res a

divisao :: Int -> Int -> ResDiv Int
divisao _ 0 = Erro
divisao n d = Res (div n d)

mult :: ResDiv Float -> ResDiv Float -> ResDiv Float
mult Erro    _       = Erro
mult _       Erro    = Erro
mult (Res x) (Res y) = x * y

-- converte um valor do tipo ResDiv para Int.
-- o valor erro é considerado 0
res2int :: ResDiv Double -> Double
res2int Erro    = 0
res2int (Res x) = x

No entanto nem era preciso definir-mos este tipo de dados. Já tinhamos o tipo Maybe a que faz exactamente a mesma coisa. A sua definição é a seguinte:

data Maybe a = Just a | Nothing

A única diferença em relação ao tipo por nós definido são mesmo os nomes.

Mas também podemos ter tipos recursivos. Vejamos como definir expressões matemáticas e uma função que as avalia.

data Exp a = Val a                -- um numero
          | Neg (Exp a)          -- o simetrico de uma expressao
          | Add (Exp a) (Exp a)  -- a soma de duas expressoes
          | Sub (Exp a) (Exp a)  -- subtraccao de duas expressoes
          | Mul (Exp a) (Exp a)  -- multiplicacao ...
          | Div (Exp a) (Exp a)  -- divisao ...


avalia (Val x)          = x
avalia (Neg exp)        = - (avalia exp)
avalia (Add exp1 exp2)  = (avalia exp1) + (avalia exp2)
avalia (Sub exp1 exp2)  = (avalia exp1) - (avalia exp2)
avalia (Mul exp1 exp2)  = (avalia exp1) * (avalia exp2)
avalia (Div exp1 exp2)  = (avalia exp1) / (avalia exp2)


Aqui têm um pequeno tutorial de Haskell, por enquanto ainda muito básico, mas espero ir acrescentando mais informações. Qualquer dúvida que tenham é só dizer.

Para terminar mostrava apenas um exemplo da função "hello world" (a sua explicação fica para outra altura...)

main :: IO ()
main = do putStrLn "Hello World"

-- ou uma que retorna 0
main :: IO Int
main = do putStrLn "Hello World"
        return 0

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Muito porreiro! :thumbsup:

Só uma curosidade, qual a razão do Haskell ser pouco eficiente?

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Isto vai dar muito jeito para uma cadeira que vou ter em breve. :thumbsup:

Muito obrigado por esta excelente intro!

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Muito porreiro! :thumbsup:

Só uma curosidade, qual a razão do Haskell ser pouco eficiente?

tendo em conta alguns post teus que já vi, penso que sabes o que é o lambda-calculus (que é a base do Haskell, como já referi).

o Haskell não é eficiente pela forma como a funções são executadas, não sei exactamente como funciona o GHC, mas o HUGS penso que calcula os resultados através de reduções dos lambda-termos, que normalmente é algo não muito eficiente, para além de que para calcular o resultado de uma simples função são necessárias várias reduções (por exemplo para fazer soma 2 4, o HUGS faz 29 reduções, coisa que em Assembly se faz com meia dúzia de instruções, algo muito mais rápido do que fazer um redução).

mas as afirmações que fiz foram sobretudo baseadas na teoria do lambda-calculus, e na forma como calculamos o resultado das funções manualmente, não sei exactamente como funcionam os compiladores.

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Muito porreiro! :thumbsup:

Só uma curosidade, qual a razão do Haskell ser pouco eficiente?

tendo em conta alguns post teus que já vi, penso que sabes o que é o lambda-calculos (que é a base do Haskell, como já referi).

o Haskell não é eficiente pela forma como a funções são executadas, não sei exactamente como funciona o GHC, mas o HUGS penso que calcula os resultados através de reduções dos lambda-termos, que normalmente é algo não muito eficiente, para além de que para calcular o resultado de uma simples função são necessárias várias reduções (por exemplo para fazer soma 2 4, o HUGS faz 29 reduções, coisa que em Assembly se faz com meia dúzia de instruções, algo muito mais rápido do que fazer um redução).

mas afirmações que fiz foram sobretudo baseadas na teoria do lambda-calculus, e na forma como calculamos o resultado das funções manualmente, não sei exactamente como funcionam os compiladores.

Percebo.

Não conheço o Haskell mas isso não será um falso problema, ou seja, isso não será apenas uma questão de optimização da implementação do próprio interpretador/compilador?

Não faltam casos de sucesso de empresas que usaram, por exemplo, Lisp, por isso é que fiquei curioso em saber porque não é eficiente.

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

penso que o Lisp (que eu nunca usei) não é uma linguagem "funcional pura", como o Haskell, e como tal usa outras estratégias mais eficientes para executar os programas.

provavelmente poderiam ser criados compiladores de Haskell mais eficientes, mas visto que se trata de uma linguagem "académica", interessa mais os conceitos que ela permite estudar do que a eficiência.

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Sim, compreendo. A finalidade é fazer a prova de conceito e não ser (mais) uma linguagem empresarial.

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

adicionada secção "Sinónimos de tipos e novos tipos de dados".

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Xii aos anos que nao via isto (coisa de 2 anos LOL) depois disso foi java e deixei logo haskell para trás mas aconselho a quem entra no mundo da programação pois é uma linguagem que ajuda a puxar pela cabeça e a idializar o programa mentarmente.

Se quizerem penso que tenho para aqui as minhas aulas de Programção I bem como o trabalho final da cadeira.

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Ainda não conhecia este tutorial mas já o estive a ler e está muito fixe! :thumbsup:

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Ainda não conhecia este tutorial mas já o estive a ler e está muito fixe! :thumbsup:

o Haskell foi uma das linguagens que influenciou o Python, aliás o Python também permite programação funcional (embora não seja uma linguagem de programação funcional pura, como o Haskell).

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Rui podes-me explicar como é que metes os then e os else p o hugs os correr? é q já tentei por em várias ocasioes mas dá sempe erro:

"Syntax error in declaration (unexpected keyword "then")"

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Rui podes-me explicar como é que metes os then e os else p o hugs os correr? é q já tentei por em várias ocasioes mas dá sempe erro:

"Syntax error in declaration (unexpected keyword "then")"

exemplo de uma função que verifica se um número é zero

iszero::Int->Bool
iszero x=if x==0 then True else False

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

axo q já tou a ver o meu erro. Eu estava a usar isso mas com as guardas |

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

axo q já tou a ver o meu erro. Eu estava a usar isso mas com as guardas |

cria um novo tópico com essa dúvida e coloca lá o código em que tens problemas.

este tópico devia ser apenas para discutir o tutorial.

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

exemplo de uma função que verifica se um número é zero

iszero::Int->Bool
iszero x=if x==0 then True else False

axo q já tou a ver o meu erro. Eu estava a usar isso mas com as guardas |

Com guardas:

iszero::Int->Bool
iszero x | x==0 = True
         | otherwise = False

On Topic: o tutorial parece interessante, vou analisar com mais detalhe.

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

As guardas não precisam de estar alinhadas.

1. Se uma linha começa mais à frente do que começou a linha anterior, então ela deve ser considerada uma continuação da linha anterior.

2. Se uma linha começa na mesma coluna que a anterior, então elas são consideradas definições independentes.

3. Se uma linha começa mais atrás do que a anterior, então essa linha não pertence à mesma lista de definições.

São estas as regras de indentação em Haskell.

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Por acaso sempre pensei que as guardas tivessem que estar alinhadas (tal como acontece com o case, ou com o do).

Mas fiquei agora a saber que as guardas até podem estar todas na mesma linha. Tipo iszero x | x==0 = True | otherwise = False.

Assim, elas seguirão as regras de indentação habituais do Haskell.

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Boas malta.

Eu hoje falei com um amigo q m indicou este forum pois eu gostaria de aprender a mexer em haskell. e eu vim ler este tópico e resolvi seguir o tutorial do Rui Carlos. Eu uso o GHCi e o VIM 7.2. eu no VIM pus , tal como indicado no tutorial:

module PrimeiroProg where

funcao :: Int -> Int

funcao x = x^2

e salvei em .hs. Ao correr isto no GHCi, se eu fizer "funcao 2" da-me 4 e por ai fora, mas se eu fizer "funcao n", aparece-me o seguinte: <interactive>:1:7: Not in scope : 'n'

eu não entendo quase nada disto por favor gostaria de alguma ajuda se possivel.

Começar a aprender haskell foi-me recomendado por um amigo que disse que era da linguagem mais simples que existe para começar a aprender.

Cumprimentos  :D

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Que resultado é que esperavas obter?

Deste-lhe uma variável sem qualquer valor associado. Ias obter o quadrado de quê?

Não podes usar variáveis/funções que não estejam definidas, caso contrário vais obter erros.

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Crie uma conta ou ligue-se para comentar

Só membros podem comentar

Criar nova conta

Registe para ter uma conta na nossa comunidade. É fácil!


Registar nova conta

Entra

Já tem conta? Inicie sessão aqui.


Entrar Agora