O Haskell é uma linguagem de programação funcional, baseada no lambda-calculus (Lambda calculus é um cálculo que modela os aspectos computacionais das funções. Estudar lambda calculus ajuda a entender os elementos da programação funcional e semântica das linguagens de programação funcionais independentemente dos detalhes de sintática de uma linguagem particular), 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 linguagens mais populares no meio acadêmico e muito usadas 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 (não precisa instalar, mas precisa fazer o atalho do arquivo winhugs.exe basta irem para Baixar Compilador Hugs 98 i386(Windows) Haskell - (sem instalação - basta apenas descompatar o arquivo). - Se você quiser fazer a instalação ir para: Baixar Compilador Hugs 98 i386(Windows) Haskell - (com instalação).
■ 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 arquivo deverá ser 'Nome_Mod.hs', embora não seja obrigatório, será necessário caso queiramos importar este módulo em um outro programa.
Para definir um comentário de uma linha usamos os caracteres '--' e para comentários de mais linhas '{-' (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 monitor de vídeo é um pouco complexo (no obriga utilizar monad's), como tal fica um exemplo da função 'f(x)=x2'
Para testar o programa basta salvar o arquivo ('PrimeiroProg.hs') e, depois de instalado o GHC (compilador), executar o comando 'ghci PrimeiroProg.hs' (no Windows normalmente até podemos clicar duas vezes sobre o ícone que o GHC se abre automaticamente).
Para testar a função faz função n onde n é um inteiro qualquer e deverão obter como resultado n2.
A terceira 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 de funções.
É muito simples definir funções matemáticas em Haskell.
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 (não fixo) podem passar a prefixo envolvendo-os com parêntesis (tal como acontece na função soma).
Reparem que na função vogal o then passa para a linha seguinte, podemos fazê-lo desde que a linha esteja identada (bastava ser um caractere).
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 se resolvem 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. Trata-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
Até aqui é fácil perceber os exemplos. Talvez os mais complicados sejam os 3 últimos onde é usado pattern matching.
Basicamente aqui o programa tenta encaixar os argumentos em padrões (neste caso verificar se coincide com 0). O caractere '_' significa "qualquer coisa", ou seja, tudo encaixa no '_'.
De referir ainda que no último exemplo não pudéssemos 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.
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' seleciona os n primeiro elementos de uma lista
dezLetras str = take 10 str
comprimento str = length str
Em 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 números 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 TODOS os números inteiros maiores do que 0
[1..]
Existem algumas funções interessantes sobre listas, como por exemplo, map, filter e foldl.
Exemplos
vezes2 n = n * 2
-- aplica a função 'vezes2' a todos os 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)
De esta forma escrever Par ou (Int,Int) é a mesma coisa. No próximo exemplo as funções f1 e f2 é exatamente a mesma coisa.
f1 :: (Int,Int) -> Int
f1 (x,y) = x + y
f2 :: Par -> Int
f2 (x,y) = x + y
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 aconteç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 ResDiv só servia quando estivéssemos que 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 este tipo de dados. Já tínhamos o tipo Maybe que faz exatamente 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 os mesmos nomes.
Mas também podemos ter tipos recursivos. Vejamos como definir expressões matemáticas e uma função que faz a avaliação.
data Exp a = Val a -- um numero
| Neg (Exp a) -- o simétrico de uma expressão
| Add (Exp a) (Exp a) -- a soma de duas expressões
| Sub (Exp a) (Exp a) -- subtração de duas expressões
| Mul (Exp a) (Exp a) -- multiplicação ...
| Div (Exp a) (Exp a) -- divisão ...
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)
Para terminar um último exemplo usando a função "hello world" .
main :: IO ()
main = do putStrLn "Hello World"
-- ou uma que retorna 0
main :: IO Int
main = do putStrLn "Hello World"
return 0
:: Bubble sort
// Esta verificação de versão de mudanças em uma etapa separada para a simplicidade do código.
// Porque Haskell não tem variáveis para fazer o controlar dos códigos.
bsort :: Ord a => [a] -> [a]
bsort s = case _bsort s of
t | t == s -> t
| otherwise -> bsort t
where _bsort (x:x2:xs) | x > x2 = x2:(_bsort (x:xs))
| otherwise = x:(_bsort (x2:xs))
_bsort s = s
// Esta versão utiliza o polimorfo.
import Data.Maybe (fromMaybe)
import Control.Monad
bsort :: Ord a => [a] -> [a]
bsort s = maybe s bsort $ _bsort s
where _bsort (x:x2:xs) = if x > x2
then Just $ x2 : fromMaybe (x:xs) (_bsort $ x:xs)
else liftM (x:) $ _bsort (x2:xs)
_bsort _ = Nothing
Fonte: professor Rui Carlos. - [Topo]