Haskell em 10 minutos
Introdução
Haskell é uma linguagem de programação funcional (isto é, tudo é feito através de definições e chamadas de função), de tipagem estática e implícita (tipos são conferidos pelo compilador, mas você não precisa declará-los), e lazy (nada é feito até que seja necessário). As outras linguagens mais parecidas provavelmente são da família de ML (que não são lazy).
O compilador mais popular de Haskell é o GHC. Você pode baixá-lo de http://www.haskell.org/ghc/download.html . Existem executáveis do GHC disponíveis para GNU/Linux, FreeBSD, MacOS, Windows, e Solaris. quando você tiver o GHC instalado, vão ter dois programas de nosso interesse: ghc e ghci. O primeiro compila código Haskell para executáveis. O segundo é um interpretador interativo que permite que você escreva código Haskell e tenha o retorno imediato.
Expressões simples
Você pode digitar várias expressões matemáticas no ghci e ter uma resposta. Prelude> é o prompt padrão do GHCi.
Prelude> 3 * 5 15 Prelude> 4 ^ 2 - 1 15 Prelude> (1 - 5)^(3 * 2 - 4) 16
Strings são escritas em "aspas." Você pode concatená-las com ++
.
Prelude> "Oi" "Oi" Prelude> "Oi" ++ ", Haskell" "Oi, Haskell"
Chamadas a funções são feitas colocando os argumentos diretamente após o nome da função. Não são usados parênteses nas chamadas:
Prelude> succ 5 6 Prelude> truncate 6.59 6 Prelude> round 6.59 7 Prelude> sqrt 2 1.4142135623730951 Prelude> not (5 < 3) True Prelude> gcd 21 14 7
O console
Ações de I/O (entrada e saída) podem ser usadas para ler e escrever no console. Algumas delas que são comuns incluem:
Prelude> putStrLn "Oi, Haskell!" Oi, Haskell! Prelude> putStr "Sem quebra de linha!" Sem quebra de linha! Prelude> print (5 + 4) 9 Prelude> print (1 < 2) True
As funções putStr
e putStrLn
escrevem strings no terminal, enquanto print
escreve qualquer tipo de valor. (Se você chamar print
com uma string, ela será escrita entre aspas.)
Se você precisa de mais de uma ação de I/O em uma expressão, você pode usar um bloco do
. Ações são separadas por ponto-e-vírgula.
Prelude> do { putStr "2 + 2 = " ; print (2 + 2) } 2 + 2 = 4 Prelude> do { putStrLn "ABCDE" ; putStrLn "12345" } ABCDE 12345
Leituras podem ser feitas com getLine
(que retorna uma String
) ou readLn
(que retorna qualquer tipo de valor que você queira). O símbolo <-
é usado para atribuir um nome ao resultado de uma ação de I/O.
Prelude> do { n <- readLn ; print (n^2) } 4 16
(O 4 foi digitado, e 16 o resultado.)
Existe uma outra forma de se escrever blocos do
. Se você não usar chaves e ponto-e-vírgula então a indentação se torna significante. Esta última forma não funciona bem no ghci, mas tente colocar o código em um arquivo (tipo, Test.hs) e compile-o.
main = do putStrLn "Quanto é 2 + 2?"
x <- readLn
if x == 4
then putStrLn "Você acertou!"
else putStrLn "Você errou!"
Você pode compilar esse código com ghc --make Test.hs, e o executável resultante será Test. (No Windows, Test.exe) De bônus, você usa uma expressão if
.
O primeiro caracter (que não espaço) após o do
é especial. Neste caso, é o p do putStrLn
. Toda linha que iniciar na mesma coluna que aquele p
é outra declaração do bloco do
. Aumentar a indentação torna a linha parte da declaração anterior, se você diminuir, acaba o bloco do
. Isso se chama "layout", e Haskell usa isso para evitar que você tenha que colocar chaves para terminar as declarações o tempo todo. (As palavras then
e o else
devem ser indentadas por causa disso : se elas iniciassem na mesma coluna elas seriam declarações separadas, o que seria errado.)
(Nota: Não indente com tabs se você estiver usando o layout. Isso tecnicamente funcionaria se seus tabs tiverem a largura de 8 espaços, mas é uma má idéia. Também não use fontes proporcionais -- o que aparentemente algumas pessoas fazem, mesmo quando programando!)
Tipos simples
Até agora, nenhuma declaração de tipo foi mencionada. Isso se deve ao fato de Haskell fazer inferência de tipos. Você normalmente não precisa declarar tipos a menos que você queira. Se você quiser declarar um tipo, usa-se ::
para fazê-lo.
Prelude> 5 :: Int 5 Prelude> 5 :: Double 5.0
Tipos (e typeclasses, discutidas depois) sempre começam com uma letra maiúscula em Haskell. Variáveis sempre começam com letras minúsculas. Essa é uma regra da linguagem, e não uma convenção de nomenclatura.
Você também pode perguntar ao ghci que tipo ele inferiu para algo. Isso é útil porque geralmente você não precisa declarar tipo algum.
Prelude> :t True True :: Bool Prelude> :t 'X' 'X' :: Char Prelude> :t "Oi, Haskell!" "Oi, Haskell!" :: [Char]
(Se você percebeu, [Char]
é outra forma de se falar de String
. Veja a seção sobre listas depois.)
As coisas ficam mais interessantes com números.
Prelude> :t 42 42 :: (Num t) => t Prelude> :t 42.0 42.0 :: (Fractional t) => t Prelude> :t gcd 15 20 gcd 15 20 :: (Integral t) => t
Esses tipos usam "type classes". Elas significam:
42
podem ser usados como qualquer tipo numérico. (Isso é porque eu pude declarar5
tanto comoInt
ouDouble
antes.)42.0
pode ser qualquer tipo fracionário, mas não integral.gcd 15 20
(que é uma chamada de função, incidentalmente) pode ser qualquer tipo integral, mas não fracionário.
Existem cinco tipos numéricos no "prelúdio" do Haskell (Prelude, a parte da biblioteca padrão que vem sem precisar importar nada):
Int
é um inteiro com ao menos 30 bits de precisão.Integer
é um inteiro com precisão ilimitada.Float
é um número de ponto flutuante de precisão simples.Double
é um número de ponto flutuante de precisão dupla.Rational
é um tipo fracionário, sem erro de arredondamento.
Todos os cinco são instâncias da type class Num
. Os primeiros dois são instâncias de Integral
, e os últimos três são instâncias de Fractional
.
Sumarizando isso tudo,
Prelude> gcd 42 35 :: Int 7 Prelude> gcd 42 35 :: Double <interactive>:1:0: No instance for (Integral Double)
O último tipo que vale a pena mencionar aqui é ()
, pronunciado "unit". Ele apenas tem um valor, também escrito como ()
e pronunciado "unit".
Prelude> () () Prelude> :t () () :: ()
Você pode ver isso de forma similar a palavra-chave void na família das linguagens C. Você pode retornar ()
de uma ação de I/O se você não quer retornar nada.
Informação estruturada
Tipos básicos podem ser combinados facilmente de duas formas: listas, que vão entre [colchetes], e tuplas, que vão entre (parênteses).
Listas são usadas para armazenar vários valores do mesmo tipo.
Prelude> [1, 2, 3] [1,2,3] Prelude> [1 .. 5] [1,2,3,4,5] Prelude> [1, 3 .. 10] [1,3,5,7,9] Prelude> [True, False, True] [True,False,True]
Strings nada mais são que listas de caracteres.
Prelude> ['O', 'i', ',', ' ', 'H', 'a', 's', 'k', 'e', 'l', 'l', '!'] "Oi, Haskell!"
O operador :
adiciona um item ao início de uma lista. (É a versão do Haskell da função cons da família de linguagens Lisp.)
Prelude> 'C' : ['O', 'i'] "COi"
Tuplas armazenam um número fixo de valores, que podem ter tipos diferentes.
Prelude> (1, True) (1,True) Prelude> zip [1 .. 5] ['a' .. 'e'] [(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e')]
O último exemplo usou zip
, uma função da biblioteca que transforma duas listas em uma lista de tuplas.
Os tipos são provavelmente o que você já espera.
Prelude> :t ['a' .. 'c'] ['a' .. 'c'] :: [Char] Prelude> :t [('x', True), ('y', False)] [('x', True), ('y', False)] :: [(Char, Bool)]
Listas são usadas bastante em Haskell. Existem várias funções que fazem coisas legais com elas.
Prelude> [1 .. 5] [1,2,3,4,5] Prelude> map (+ 2) [1 .. 5] [3,4,5,6,7] Prelude> filter (> 2) [1 .. 5] [3,4,5]
Existem duas funções legais para pares ordenados (tuplas de dois elementos):
Prelude> fst (1, 2) 1 Prelude> snd (1, 2) 2 Prelude> map fst [(1, 2), (3, 4), (5, 6)] [1,3,5]
Veja também como trabalhar com listas.
Definições de função
Nós escrevemos a definição de uma ação de I/O anteriormente, chamada main
:
main = do putStrLn "Quanto é 2 + 2?"
x <- readLn
if x == 4
then putStrLn "Você acertou!"
else putStrLn "Você errou!"
Agora vamos adicionar a isso, escrevendo uma definição de função e chamá-la de factorial
. E eu também vou adicionar um cabeçalho de módulo, que é uma melhor prática.
module Main where
factorial n = if n == 0 then 1 else n * factorial (n - 1)
main = do putStrLn "Quanto é 5! ?"
x <- readLn
if x == factorial 5
then putStrLn "Você acertou!"
else putStrLn "Você errou!"
Compile de novo o código usando ghc --make Test.hs. E,
$ ./Test Quanto é 5! ? 120 Você acertou!
Isso é uma função. Assim como as funções incluídas na biblioteca padrão da linguagem, ela pode ser chamada com factorial 5
sem precisar dos parênteses.
Agora pergunte qual é o tipo ao ghci.
$ ghci Test.hs << banner do GHCi >> Ok, modules loaded: Main. Prelude Main> :t factorial factorial :: (Num a) => a -> a
Tipos de função são escritos com o tipo do argumento, então ->
, então o tipo do resultado. (Este também tem a type class Num
.)
A função factorial pode ser simplificada, re-escrita com análise de caso.
factorial 0 = 1
factorial n = n * factorial (n - 1)
Sintaxe conveniente
Existem mais algumas coisas que ajudam na sintaxe.
secsToWeeks secs = let perMinute = 60
perHour = 60 * perMinute
perDay = 24 * perHour
perWeek = 7 * perDay
in secs / perWeek
A expressão let
define nomes temporários. (Aqui usando layout de novo. Você pode usar {chaves}, e separar os nomes com ponto-e-vírgula, se preferir.)
classify age = case age of 0 -> "newborn"
1 -> "infant"
2 -> "toddler"
_ -> "senior citizen"
A expressão case
faz um condicional de múltiplas ramificações. O label especial _
significa "qualquer outra coisa".
Usando bibliotecas
Tudo usado até agora neste tutorial é parte do módulo Prelude, que é o conjunto de funções Haskell que estão sempre disponíveis em qualquer programa.
O melhor caminho para se tornar um programador Haskell bem produtivo (além de prática!) é conhecer as outras bibliotecas que fazem o que você precisa. A documentação da biblioteca padrão está em http://haskell.org/ghc/docs/latest/html/libraries/. Lá tem módulos com:
- Estruturas de dados úteis
- Programação paralela e concorrente
- Bibliotecas gráficas e de GUI
- Redes, POSIX e outras coisas de nível de sistema
- Duas frameworks de testes, QuickCheck e HUnit
- Expressões regulares e parsers preditivos
- Mais ...
module Main where
import qualified Data.Map as M
errorsPerLine = M.fromList
[ ("Carlos", 472), ("Dani", 100), ("Saulo", -5) ]
main = do putStrLn "Quem é você?"
name <- getLine
case M.lookup name errorsPerLine of
Nothing -> putStrLn "Eu não te conheço."
Just n -> do putStr "Erros por linha: "
print n
O import
diz para usar código de Data.Map
e que ele será prefixado por M
. (Isso é preciso porque algumas das funções de Data.Map
tem os mesmos nomes de funções no Prelude
. A maior parte das bibliotecas não precisam do trecho do as
.)
Se você precisa de algo que não exista na biblioteca padrão, tente procurar em http://hackage.haskell.org/packages/hackage.html ou na página de aplicações e bibliotecas desta wiki. Essa é uma coleção de muitas bibliotecas diferentes escritas por muita gente. Uma vez que você tenha uma biblioteca, extraia ela, vá ao seu diretório e faça o seguinte para instalar:
runhaskell Setup configure runhaskell Setup build runhaskell Setup install
Em um sistema UNIX, você provavelmente vai precisar ser root para fazer a última parte.
Tópicos que excedem nosso limite de 10 minutos
- Advanced data types|Tipos de dados avançados
- Listas aritméticas
- Comprehensions em listas
- Sinônimos de tipagem
- data vs newtype (and here)
- Type classes e instâncias
- Sintaxe avançada
- Funções avançadas
- Monads
- I/O em arquivos
- Ler arquivos
- Gravar arquivos