Haskell em 10 minutos

From HaskellWiki
Revision as of 23:44, 15 May 2012 by Vazio (talk | contribs) (→‎O console)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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 declarar 5 tanto como Int ou Double 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:

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

Línguas: en zh/cn ja