Haskell: Introdução ao Paradigma Funcional

Básico

Operadores Básicos

    + - * / mod sqrt

Operadores Lógicos

    && || not

Operadores Relacionais

    > < >= <= == /=

Declarando Valores

    x = a

Comentários

    -- linha comentada
    {-
    - bloco comentado  
    -}

Funções

Aplicação

    f x y == (f x) y == x 'f' y

Expressões Condicionais

    nomeFuncao param = if cond then resultado1 else resultado2

Equações Guardadas

    nomeFuncao param | cond = resultado1
                     | otherwise = resultado2

Expressões Case

    funcao xs = case expression of pattern1 -> result1
                               pattern2 -> result2

Definição local

    -- relacionamos expressões com variáveis, melhorando visibilidade e escopo locais
    -- (where)
    funcao xs = ...
      where xs = ...
    -- (let)
    funcao xs = let <bindings> in <expression>

Composição de Funções

    f (g xs) == (f . g) xs

Listas

Definição Recursiva

    array = []
    array = head:array

Definição Funcional

    arrayCompleto = (x:xs)
    arraySemCabeca = (_:xs)
    arraySemCauda = (x:_)

Lembre-se

Strings são arrays de caracteres.

Operadores

    -- (cons) define uma lista
    :
    -- (concatenação)                
    ++
    -- (seleção) indexação
    !!
    -- comparação a partir do primeiro        
    > < >= <= == /=

Funções Básicas

    -- primeiro elemento
    head xs
    -- exceto primeiro elemento
    tail xs
    -- ultimo elemento
    last xs
    -- exceto ultimo elemento
    init xs
    -- tamanho
    length xs
    -- verifica se é vazia
    null xs
    -- inverte
    reverse xs
    -- do início até k-ésima posição
    take k xs
    -- da k-ésima posição até fim
    drop k xs
    -- maior
    maximum xs
    -- menor
    minimum xs
    -- soma dos elementos
    sum xs
    -- produto dos elementos
    product xs
    -- verifica se x pertence a xs
    elem x xs

Funções para Definição de Listas

    -- (range) lista de k até m
    [k..m]
    -- (range) lista de k até m seguindo sequência k,p
    [k,p..m]
    -- (range) lista infinita
    [k..]
    -- circulariza infinitamente
    cycle xs
    -- produzir lista infinita com um elemento
    repeat x
    -- lista de tamanho k com elementos x   
    replicate k x

Outras Funções

    -- aplicar função f a todos os elementos da lista xs
    map f xs
    -- lista com elementos que satrisfazem condição
    filter cond xs
    -- na lista xs, substitui o cons(:) pela função f e a lista vazia pelo valor inicial x
    -- associativo à direita (não trabalha com listas infinitas)
    foldr f x xs
    -- associativo à esquerda
    foldl f x xs  

Compreensão de Listas

    [ x | x <- array, cond]

Tuplas

Definição

    (a,b)

Funções

    -- primeiro Elemento
    fst (a,b) == a
    -- segundo Elemento
    snd (a,b) == b
    -- junta duas listas em uma lista de pares (índice com índice)
    zip xs ys

Tipos Ordenáveis

Definição

    -- podemos definir um tipo ordenável no escopo da função
    funcao :: (Ord a)
    -- podemos retornar um Ordering
    funcao :: Ordering
    GT -- maior que
    EQ -- igual
    LT -- menor que

Lambda Calculus

Conceito

Possui semântica de linguagens de programação. Surgiu na lógica matemática, baseada na Teoria das funções, a qual consiste em manipular funções de forma puramente sintática.

Contribuições: 1. Poder de representação, veículo de estudo de semântica de conceitos de linguagens de programação; 2. Todas linguagens funcionais são vistas como variações do lambda calculus; 3. Semântica denotacional usa conceitos do lambda calculus; 4. Menor linguagem de programação universal existente: Uma regra de definição de função e uma regra de transformação (substituição).

Sintaxe

<expressão> | <variável>                 : identificadores minúsculos
            | <constante>                : objetos pré-definidos
            | (<expressão> <expressão>)  : combinações
            | (\<variável>.<expressão>)  : abstrações

Expressões Lambda em Haskell

(\x -> f x) y  ==  f y  

Recursões em Expressões Lambda

Para tal, fazemos uso do Ponto Fixo, que é o ponto mapeado para ele mesmo pela função. Nem toda função possui ponto fixo, mas existem funções que podem ter mais de um ponto fixo.

Combinador de Ponto Fixo em Haskell

Implementação

fix :: (a -> a) -> a
fix f = f (fix f)                -- lambda lifted

-- alternativamente:

fix' f = let x = f x in x        -- lambda dropped

Aplicação

funcao x = fix funcao'
    where   funcao' <caso base>
            funcao' <caso recursivo>

-- alternativamente:

funcao = \x -> if (<caso base>) then <caso base> else <caso recursivo>  

Tipos de Dados

Provém representação de estruturas comuns da programação. As linguagens possuem tipos básicos, mas é desejável criar suas próprias estruturas de dados.

Sintaxe

data NomeDoTipo tipoParam1 tipoParam 2 = NomeConstrutor tipoParam1 tipoParam 2

Múltiplos construtores: lidar com situações particulares de um tipo de dado.

data NomeDoTipo tipoParam = Construtor1 | Construtor2 tipoParam

Tipos de Dados Recursivos

data List a = Nil | Cons a (List a) deriving (Eq,Show)
data BinaryTree a = NIL | Node a (BinaryTree a) (BinaryTree a) deriving (Eq,Show)

Conjuntos Enumerados

Tipos de Dados com finitos valores possíveis. Um exemplo clássico é o Color:

data Color
= Red
| Orange
| Yellow
| Green
| Blue
| Purple
| White
| Black
| Custom Int Int Int -- R G B components

Sinônimos

Criação de novos tipos usando tipos pré-existentes, melhorando legibilidade.

type ID = Int
type BadType = Int -> BadType  -- Tipo Infinito
type List3D a = [(a,a,a)]      -- Tipo Parametrizado

Classes (Typeclasses)

Definem interfaces genéricas com feições comuns (análogo aos serviços). Conceito análogo a Objetos em Programação Orientada a Objetos.

-- Caso Geral e Assinaturas
-- Definição minimamente completa
class Servico a where
      metodo :: a -> a -> Int

-- Caso Particular
instance Servico bool where
      metodo True = 1
      metodo False = 0

Classes Padrão (Built-in Typeclasses)

deriving (Eq, Show, Ord, Read, Bounded...)

Lembre-se

Podemos usar typeclasses para adicionar contexto, como no exemplo abaixo:

isEquals :: Eq a => a -> a -> bool

Módulos

Usados para particionar ou agrupar sub componentes do sistema, melhorando sua modularidade. São semelhantes a pacotes.

Exportação

module Cards ( Tipo1(),                         -- sem construtores
               Tipo2(Construtor1, Construtor2), -- alguns construtores
               Tipo3(..),                       -- todos os construtores
               funcao
             )
  where

data Tipo1 = ...
data Tipo2 = ...
data Tipo3 = ...
funcao :: ... -> ...
funcao = ...

Importação

Importando pacotes

import NomeModulo                   -- Tudo
import NomeModulo(funcao)           -- Apenas funcao
import NomeModulo hiding (funcao)   -- Tudo exceto funcao
import qualified NomeModulo as m    -- Tudo, mas chamando NomeModulo de m
import Pacote.Modulo as m           -- Importação Hierárquica

Utilizando importações

NomeModulo.funcao
NomeModulo.Tipo

Entrada e Saída (IO)

-- Main: ação IO com tipo IO(). Todo programa Haskell que precisa fazer IO começa sua execução com essa ação
-- Do: forma conveniente de definir uma sequencia de ações

main :: IO()
main = do
  -- Código IO

Lembre-se

A função read pode nos ajudar a converter tipos, o que é bastante útil quando estamos lidando com entrada e saída:

read a :: (Read tipo) => tipo

Segue adiante o básico que é fornecido pela biblioteca IO ao utilizarmos import IO:

data IOMode = ReadMode | WriteMode| AppendMode | ReadWriteMode
type FilePath = String

-- Read
openFile :: FilePath -> IOMode -> IO Handle
hClose :: Handle -> IO ()
hIsEOF :: Handle -> IO Bool
hGetChar :: Handle -> IO Char
hGetLine :: Handle -> IO String
hGetContents :: Handle -> IO String
getChar :: IO Char                                    -- Obter caractere da entrada
getLine :: IO String                                  -- Obter linha da entrada
getContents :: IO String

-- Write
hPutChar :: Handle -> Char -> IO ()
hPutStr :: Handle -> String -> IO ()
hPutStrLn :: Handle -> String -> IO ()
putChar :: Char -> IO ()
putStr :: String -> IO ()                              -- Printar linha
putStrLn :: String -> IO ()                            -- Printar e pular linha
readFile :: FilePath -> IO String
writeFile :: FilePath -> String -> IO ()

Manipulação de Erros

Pura

-- Maybe
data Maybe a = Just a | Nothing deriving (Eq, Ord)

-- Either
data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)

Exceções

-- Retorna um IO() ou a mensagem de uma exceção
try :: Exception e => IO a -> IO (Either e a)

-- Permite realizar uma ação quando acontece exceção
handle :: Exception e => (e -> IO a) -> IO a -> IO a

-- Permite levantar exceções
throwIO :: Exception e => e -> IO a

-- Permite levantar erros
ioError :: IOError -> IO a

Testes

HUNit

-- importando HUnit

import Test.HUnit

-- elementos importantes

data Test = TestCase Assertion 	    -- um caso de teste simples
          | TestList [Test]		      -- uma lista de casos de teste
          | TestLabel String Test		-- um teste indentificado por um label

data Counts = Counts { cases, tried, errors, failures :: Int }  -- contadores de testes
            deriving (Eq, Show, Read)


-- funções baseadas em casos de teste

testCaseCount :: Test -> Int
testCasePaths :: Test -> [Path]

-- funções baseadas em afirmação

assertFailure :: String -> Assertion
assertFailure msg = ioError (userError ("HUnit:" ++ msg))

assertBool :: String -> Bool -> Assertion
assertBool msg b = unless b (assertFailure msg)

assertString :: String -> Assertion
assertString s = unless (null s) (assertFailure s)

assertEqual :: (Eq a, Show a) => String -> a -> a -> Assertion

-- funções baseadas em contadores

runTestText :: PutText st -> Test -> IO (Counts, st)
showCounts :: Counts -> String
showPath :: Path -> String
runTestTT :: Test -> IO Counts  

Concorrência e Paralelismo

Compilando programa para arquiteturas paralelas:

ghc --make -threaded codigo.hs

Execução:

## sendo x = quantidade de núcleos utilizados
codigo +RTS -Nx

Módulos e funções envolvidas:

-- importando o módulo de paralelismo
import Control.Parallel

-- funções
par :: a −> b −> b -- avalia argumentos em paralelo. Retorna como resultado o valor do
segundo argumento. par a b = b. escrita as vezes como a ‘par’ b
pseq :: a −> b −> b