Programação Funcional com Haskell Fabrício Olivetti de França Universidade Federal do ABC
Paradigma Funcional
Paradigmas Em muitos cursos de Computação e Engenharia iniciam com paradigma imperativo. Exemplo clássico da receita de bolo. 1
Paradigma Imperativo int pares[10]; for (int i=0; i<10; i++) { pares[i] = 2*i; } 2
Paradigma Imperativo Descrevo passo a passo o que deve ser feito. Infame goto. Evoluiu para o procedural e estruturado com if, while, for. 3
Paradigma Imperativo int pares[10]; for (int i=0; i<10; i++) { pares[i] = dobro(); } int dobro () { static int i = 0; ++i; return i; } 4
Problemas Ao seguir o passo a passo, você chega no resultado...mas não tem ideia de qual será ele. Qualquer um pode usar suas variáveis globais e suas funções, para o seu uso intencional ou não... 5
Orientação a Objetos Outro paradigma muito estudado em cursos de Computação. Encapsula dados com seus próprios métodos. Evita que certas informações e procedimentos sejam utilizados fora de seu contexto. 6
Orientação a Objetos class Pares { private int i; // n~ao quero ninguém mudando meu estado private ArrayList lista; public Pares() { i=0; lista = new ArrayList(); } public void addone() { ++i; lista.add(2*i); } public ArrayList get(){ return lista; } } 7
Orientação a Objetos class Main { public static void main(string[] args) { Pares l1 = new Pares(); l1.addone(); l1.addone(); l1.addone(); System.out.println(l1.get()); } } 8
Problemas 9
Problemas Nos textos didáticos temos exemplos simples e criados para ilustrar quando faz sentido termos objetos. Nem sempre isso representa a realidade. Composição de funções através de herança = bagunça! 10
Problemas Encapsula códigos imperativos para segurança e reuso, mas não evita os bugs na criação das classes. Usa estado intensivamente, incentiva mutabilidade e não-determinismo. Difícil de paralelizar. 11
Efeito colateral A execução da instrução atual depende do estado atual do sistema. Ex.: dobro() e addone() vai depender do estado atual de i. 12
Efeito colateral Eu posso executar a função dobro() para o primeiro e o segundo elemento em paralelo?? 13
Paradigma Funcional Computação = avaliação de composição de funções. Evita estados. Declarativo. 14
Paradigma Funcional take 10 [2*i i <- zplus] -- pegue 10 elementos da lista formada pelo -- dobro dos números inteiros positivos. 15
Paradigma Funcional Note que i não é uma variável, não muda de valor, é apenas um símbolo da definição. 16
Funções Puras Linguagens funcionais incentivam (ou obrigam) a criação de funções puras. Ao chamar a função com o mesmo argumento, sempre terá a mesma resposta. Se não temos efeito colateral......e o resultado de uma expressão pura não for utilizado, não precisa ser computado....o programa como um todo pode ser reorganizado e otimizado....é possível computar expressões em qualquer ordem (ou em paralelo). 17
Funções Puras double media (int * valores, int n) { double soma = 0; int i; for (i = 0; i < n; i++) soma_valor(&soma, valores[i]); return soma / n; } void soma_valor (double * soma, int valor) { soma += valor; } 18
Programação sem bugs A ausência de estados permite evitar muitos erros de implementação. O lema da linguagem Haskell: se compilou, o código está correto! 19
Iterações vs Recursões Em linguagens funcionais os laços iterativos são implementados via recursão, geralmente levando a um código enxuto e declarativo. 20
Iterações vs Recursões int gcd (int m, int n) { int r = m % n; while(r!= 0) { m = n; n = r; r = m%n; } return m; } 21
Iterações vs Recursões mdc 0 b = b mdc a 0 = a mdc a b = mdc b (a rem b) 22
Avaliação Preguiçosa Algumas linguagens funcionais implementam o conceito de avaliação preguiçosa. Quando uma expressão é gerada, ela gera uma promessa de execução. Se e quando necessário, ela é avaliada. 23
Avaliação Preguiçosa int main () { int x = 2; f(x*x, 4*x + 3); return 0; } int f(int x, int y) { return 2*x; } 24
Avaliação Preguiçosa int main () { int x = 2; f(2*2, 4*2 + 3); return 0; } int f(int x, int y) { return 2*x; } 25
Avaliação Preguiçosa int main () { int x = 2; f(4, 4*x + 3); return 0; } int f(int x, int y) { return 2*x; } 26
Avaliação Preguiçosa int main () { int x = 2; f(4, 11); return 0; } int f(int x, int y) { return 2*x; } 27
Avaliação Preguiçosa int main () { int x = 2; 8; return 0; } int f(int x, int y) { return 2*x; } 28
Avaliação Preguiçosa f x y = 2*x main = do let z = 2 print (f (z*z) (4*z + 3)) 29
Avaliação Preguiçosa f x y = 2*x main = do let z = 2 print (2 * (z*z)) 30
Avaliação Preguiçosa f x y = 2*x main = do let z = 2 print (8) A expressão 4 z + 3 nunca foi avaliada! 31
Avaliação Preguiçosa Isso permite a criação de listas infinitas: [2*i i <-[1..]] 32
Haskell
Haskell: ghc e ghci Glasgow Haskell Compiler: compilador de código aberto para a linguagem Haskell. Possui um modo interativo ghci (similar ao ipython). 33
Glasgow Haskell Compiler Uso recomendado de: Git - controle de revisão Cabal - gerenciamento de projeto e dependências Haddock - documentação $ mkdir hello $ cd hello $ mkdir bin $ vim Hello.hs 34
Primeiro projeto Hello.hs: -- -- Copyright (c) 2017 Nome - site -- GPL version 3 or later -- (see http://www.gnu.org/copyleft/gpl.html) -- -- Módulo principal module Main where -- Funç~ao principal main :: IO () main = do print ("Hello World") 35
Primeiro projeto Indica que esse é o módulo principal que irá gerar o executável. module Main where 36
Primeiro projeto Função principal é uma função que não recebe parâmetros e retorna uma entrada e/ou saída de arquivos. main :: IO () 37
Primeiro projeto Início do programa: main = do 38
Primeiro projeto Instrução de entrada/saída de arquivos (para stdout): print ("Hello World") 39
Primeiro projeto $ git init $ git add Hello.hs $ git commit -am "olá mundo funcional!" 40
Primeiro projeto $ ghc -o bin/hello Hello.hs $./bin/hello 41
Primeiro projeto Para saber mais Criando um projeto usando Cabal (projetos grandes, não didáticos): https://wiki.haskell.org/how to write a Haskell program 42
Para a disciplina Criem um repositório no github com o nome BIGDATA2018, coloquem todos seus códigos lá! Os códigos dados em aula estarão em: http://github.com/folivetti/bigdata. 43
Tipos Básicos
Tipos de Dados Haskell tem a tipagem forte e estática. Você não pode misturar tipos e esses devem ser bem definidos: let x = 1 :: Integer x + "texto" -- erro! N~ao pode somar um número a um texto x * 2.0 -- erro! O valor inteiro n~ao se transforma em real x * 2 -- ok! 44
Tipos numéricos Int: inteiros de 32 ou 64 bits Integer: Inteiros de precisão arbitrária Float: ponto-flutuante precisão simples Double: ponto-flutuante precisão dupla 45
Operadores soma x y = x + y subtrai x y = x - y multiplica x y = x * y divide x y = x / y Prelude> :t (+) (+) :: Num a => a -> a -> a 46
Operadores soma x y = x + y subtrai x y = x - y multiplica x y = x * y divide x y = x / y Prelude> :t (/) (/) :: Fractional a => a -> a -> a 47
Operadores div/mod - arredonda na direção de quot/rem - arredonda na direção de 0 divideint1 x y = x div y divideint1 x y = x quot y resto1 x y = x mod y resto2 x y = x rem y 48
Operadores elevado1 x y elevado2 x y = x ^ y = x ** y (^) :: (Num a, Integral b) => a -> b -> a (**) :: Floating a => a -> a -> a 49
Classes de tipos Integral: todos os tipos inteiros Floating: todos os tipos reais Num: todos os números 50
Exemplo: razão áurea O ghc infere o tipo de acordo com suas operações. aurea = (1 + sqrt 5) / 2 51
Exercício Qual o tipo dessa expressão? aurea = (1 + sqrt 5) / 2 52
Exercício Qual o tipo dessa expressão? aurea :: Floating a => a aurea = (1 + sqrt 5) / 2 (poderia ser Double, né?) 53
Bool data Bool = True False 54
Bool igual x y = x == y diferente x y = x /= y maior x y = x > y menor x y = x < y maiorigual x y = x >= y menorigual x y = x <= y 55
Bool True && True == True True && False == False True False == True False False == False not True == False 56
Ano bissexto bissexto ano = (ano rem 400 == 0) ((ano rem 4 == 0) && (ano rem 100 /= 0)) Na prática podemos deixar o código mais enxuto... 57
Tuplas let x = (1, "palavra") Prelude> fst x 1 Prelude> snd x "palavra" 58
Funções
Funções O Haskell é baseado no Lambda Calculus em que a computação é baseada em funções de uma variável. Toda função recebe uma entrada e retorna uma saída! somaum :: Integer -> Integer somaum x = x + 1 59
Funções de Uma Variável somaum Nome da função deve começar com caixa baixa. O estilo padrão é o camelcase. 60
Funções de Uma Variável somaum :: Definição dos tipos de entrada e saída. 61
Funções de Uma Variável somaum :: Integer Recebe um valor inteiro. 62
Funções de Uma Variável somaum :: Integer -> Integer Retorna um valor inteiro. 63
Funções de Uma Variável somaum :: Integer -> Integer somaum x A função, dado um valor x... 64
Funções de Uma Variável somaum :: Integer -> Integer somaum x =...é definida como... 65
Funções de Uma Variável somaum :: Integer -> Integer somaum x = x + 1...a expressão x + 1. 66
Mais de uma variável Funções de mais de uma variável, na verdade são composições de funções: soma x y = x + y Na verdade é: soma x y = (soma x) (y) -- resultado de soma x aplicado em y soma x y = \z -> x + z (y) 67
Mais de uma variável soma :: Integer -> (Integer -> Integer) soma é uma função que recebe um inteiro e retorna uma função que recebe um inteiro e retorna um inteiro. Para facilitar removemos os parênteses e fazemos a leitura como funções de múltiplas variáveis. 68
Constantes Quando fazemos: pi = 3.14 Não estamos declarando uma constante, mas sim uma função que não recebe parâmetros e retorna sempre o valor 3.14. 69
Constantes Quando fazemos: pi :: Double pi = 3.14 Não estamos declarando uma constante, mas sim uma função que não recebe parâmetros e retorna sempre o valor 3.14. 70
Assinatura soma :: Integer -> Integer -> Integer Essa é a assinatura da função, ela costuma dizer muito sobre o que ela faz. 71
Assinatura de classes Quando queremos criar uma função que recebe classes de tipos (ex.: Num) fazemos: soma :: Num a => a -> a -> a Se x e y forem inteiros, a função retornará um inteiro; se forem Double, ela retornará Double; se forem diferentes entre si, retornará um erro! 72
Assinatura genéricas Podemos também criar funções que recebem qualquer tipo: f :: a -> a -> b Função que recebe duas variáveis de um mesmo tipo qualquer e retorna uma variável de outro tipo. Aqui a, b são apenas nomes genéricos, pode ser qualquer outra letra. 73
Exercício Qual a única função possível com a seguinte assinatura? f :: a -> a 74
Exercício Qual a única função possível com a seguinte assinatura? f :: a -> a f x = x 75
Exercício Crie uma função que receba 3 valores do tipo Double: a, b, c e retorne as raízes da equação: a x 2 + b x + c. 76
Exercício Qual a assinatura dela? 77
Exercício raizseggrau :: Double -> Double -> Double -> (Double, Double) 78
Exercício Escreva a função! 79
Exercício raizseggrau :: Double -> Double -> Double -> (Double, Double) raizseggrau a b c = ((-b - (sqrt (b**2-4*a*c)))/(2*a), (-b + (sqrt (b**2-4*a*c)))/(2*a)) 80
Beleza prometida Podemos segmentar as definições de expressões através da instrução where. f x = expr where expr = expr1 + expr2 81
Beleza prometida O Haskell (assim como o Python) usa espaços para definir blocos. Lembre-se de manter seu código alinhado! f x = expr where expr = expr1 + expr2 82
Beleza prometida Muito melhor! raizseggrau :: Double -> Double -> Double -> (Double, Double) raizseggrau a b c = (x1, x2) where x1 = (-b - sqrt delta) / (2*a) x2 = (-b + sqrt delta) / (2*a) delta = b**2-4*a*c 83
Beleza prometida Mas e se delta for negativo??? raizseggrau :: Double -> Double -> Double -> (Double, Double) raizseggrau a b c = (x1, x2) where x1 = (-b - sqrt delta) / (2*a) x2 = (-b + sqrt delta) / (2*a) delta = b**2-4*a*c 84
Guard!! Os guards ( ) criam um desvio condicional para tratar certas possibilidades da sua função: f x cond1 = expr1 cond2 = expr2 otherwise = expr3 where expr1 =... expr2 =... expr3 =... Leia: a função f recebe um parâmetro x e, se a condição cond1 for verdadeira, retorne expr 1, se cond2 for verdadeira, retorne expr 2, caso contrário, retorne expr 3. Equivalente a um switch..case. 85
Guard!! raizseggrau :: Double -> Double -> Double -> (Double, Double) raizseggrau a b c delta < 0 = error "Raízes negativas!" otherwise = (x1, x2) where x1 = (-b - sqrt delta) / (2*a) x2 = (-b + sqrt delta) / (2*a) delta = b**2-4*a*c 86
Exercício Reescreva a seguinte função utilizando os conceitos aprendidos até então: bissexto ano = (ano rem 400 == 0) ((ano rem 4 == 0) && (ano rem 100 /= 0)) 87
Exercício bissexto ano ano divide 400 = True ano divide 4 = not $ ano divide 100 where divide x y = x rem y == 0 88
Pattern Matching É possível também prever alguns casos triviais de entrada. Considere a função: mult :: Num a => a -> a -> a mult x y = x * y 89
Pattern Matching Sabemos que x*1 = x 1*y = y 0*x = x*0 = 0 90
Pattern Matching mult :: (Eq a, Num a) => a -> a -> a mult 1 y = y mult x 1 = x mult 0 _ = 0 mult _ 0 = 0 mult x y = x * y 91
Pattern Matching Classe Eq indica que são números que permitem comparação de igualdade. mult :: (Eq a, Num a) => a -> a -> a mult 1 y = y mult x 1 = x mult 0 _ = 0 mult _ 0 = 0 mult x y = x * y 92
Pattern Matching significa não me importo com esse valor mult :: (Eq a, Num a) => a -> a -> a mult 1 y = y mult x 1 = x mult 0 _ = 0 mult _ 0 = 0 mult x y = x * y Os padrões são avaliados de cima para baixo até que um caso verdadeiro seja encontrado. 93
Exercício Use Pattern Matching para definir a função soma: soma :: (Eq a, Num a) => a -> a-> a soma x y = x + y 94
Exercício Use Pattern Matching para definir a função soma: soma :: (Eq a, Num a) => a -> a-> a soma 0 y = y soma x 0 = x soma x y = x + y 95
Composição de funções Digamos que temos duas funções: Uma para calcular a nota numérica final Outra para transformar a nota em conceito 96
Composição de funções mediafinal :: Double -> Double -> Double mediafinal p1 p2 = 0.4*p1 + 0.6*p2 conceito :: Double -> Char conceito media media < 5 = F media < 6 = D media < 7 = C media < 8 = B otherwise = A 97
Composição de funções Para compor as duas funções podemos definir: a$b indica que queremos aplicar a no resultado de b. geraconceito :: Double -> Double -> Char geraconceito p1 p2 = conceito $ mediafinal p1 p2 98
Composição de funções Funções de uma variável podem ser compostas com o operador. nota :: Double -> Double nota x = x*2 conceito :: Double -> Char conceito x x > 5 = A otherwise = F calcconceito = conceito. nota 99
Exercício Dada a função aocubo e a função sqrt, utilize composição de funções para calcular a expressão: aocubo :: Num a => a -> a aocubo x = x^3 f (x) = (x + y) 3 100
Exercício Dada a função aocubo e a função sqrt, utilize composição de funções para calcular a expressão: aocubo :: Num a => a -> a aocubo x = x^3 f (x, y) = (x + y) 3 f :: Floating a => a -> a -> a f x y = sqrt. aocubo $ x + y 101
Recursão A recursividade permite expressar ideias declarativas. Composta por um ou mais casos bases (para que ela termine) e a chamada recursiva. n! = n.(n 1)! 102
Recursão Caso base: 1! = 0! = 1 103
Recursão Para n = 3: 3! = 3. 2! = 3. 2. 1! = 3. 2. 1 = 6 104
Recursão fatorial :: Integer -> Integer fatorial 0 = 1 fatorial 1 = 1 fatorial n = n * fatorial (n-1) 105
Recursão fatorial :: Integer -> Integer fatorial 0 = 1 fatorial 1 = 1 fatorial n = n * fatorial (n-1) Casos bases primeiro!! 106
Estouro de pilha Em muitas linguagens, a chamada recursiva suspende a operação, armazena o estado atual em uma pilha e tenta computar a chamada recursiva. Isso pode levar ao estouro de pilha. 107
Estouro de pilha No haskell ele não armazena o estado atual na pilha, mas expande a expressão a ser calculada. Efetivamente ele faz: 5! = 5 * 4! = 5 * 4 * 3!... 108
Estouro de pilha A pilha de expressão pode estourar! Recursão caudal também é útil no Haskell. 109
Estouro de pilha fatorial :: Integer -> Integer fatorial 0 = 1 fatorial 1 = 1 fatorial n = fatorial n 1 where fatorial 1 r = r fatorial n r = fatorial (n-1) (n*r) 110
Multiplicação Etíope A multiplicação Etíope de dois números m, n é dado pela seguinte regra: Se m for par, o resultado é a aplicação da multiplicação em m/2, n 2. Se m for ímpar, o resultado a aplicação da multiplicação em m/2, n 2 somados a n. Se m for igual a 1, retorne n. 111
Multiplicação Etíope Exemplo: m n r 14 12 0 7 24 24 3 48 72 1 96 168 112
Multiplicação Etíope Implemente o algoritmo recursivo da Multiplicação Etíope. Em seguida, faça a versão caudal. 113
Multiplicação Etíope etiope :: Integer -> Integer -> Integer etiope m n m==1 = n par m = etiope (m div 2) (n * 2) otherwise = n + (etiope (m div 2) (n * 2)) 114
Multiplicação Etíope etiope :: Integer -> Integer -> Integer etiope m n = etiope m n 0 where etiope m n r m==1 = n + r par m = etiope (m div 2) (n * 2) r otherwise = etiope (m div 2) (n * 2) (r+n) 115
Listas
Listas Definição recursiva: ou é uma lista vazia ou um elemento do tipo genérico a concatenado com uma lista de a. data [a] = [] a : [a] (:) - concatenação de elemento com lista 116
Criando listas lista = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] lista1 = [1..10] -- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] lista2 = [0,2..10] -- [0, 2, 4, 6, 8, 10] lista3 = [0,2..] -- [0, 2, 4, 6, 8, 10,..] 117
Criando listas -- [2..20] lista = [ 2*x x <- [1..10] ] -- [0,2,4..] lista = [ 2*x x <- [0..] ] lista = [ expr x x <- lista ] 118
Criando listas -- [ (1*1), (1*2),..., (2*1), (2*2)... ] -- produto cartesiano lista = [ x*y x <- [1..10], y <- [1..10] ] lista = [ expr x y x <- lista1, y <- lista2 ] 119
Criando listas -- [5, 10, 15..] lista = [ x x <- [1..100], x mod 5 == 0] lista = [ expr x x <- lista, se expr x for verdadeiro ] 120
Criando listas -- [1,2..] lista = 1 : prox lista where prox (x:resto) = (x+1) : prox resto (x : resto) é um pattern match que captura o primeiro elemento da lista em x e o restante da lista em resto. 121
Criando listas -- [1,2..] lista = 1 : prox lista lista = 1 : prox [1] lista = 1 : prox (1:[]) lista = 1 : (1+1) : prox [(1+1)]... 122
Criando listas Fibonacci: fib = 1 : 2 : prox fib where prox (x : t@(y:z)) = (x+y) : prox t O padrão (x : t@(y : )) captura: x é o primeiro elemento da lista. t é o restante da lista (tail). @ faz um pattern matching em t. y é o segundo elemento da lista. z é a lista sem os dois primeiros elementos. 123
Exercício No jogo FizzBuzz, todo múltiplo de 3 é substituído por Fizz, todo múltiplo de 5 é substituído por Buzz e todo múltiplo de 3 e 5 é substituído por FizzBuss. Crie uma lista com os elementos que não serão substituídos. 124
Exercício notfizzbuzz = [ x x <- [1..], x rem 5 /= 0, x rem 3 /= 0 ] 125
Operações com listas
Concatenação lista1 = [2,3,4] lista2 = [1,2,3] x = 1 lista3 = lista1 ++ lista2 -- [2,3,4,1,2,3] lista4 = x : lista1 -- [1,2,34] (:) : a -> [a] -> [a] (++) : [a] -> [a] -> [a] 126
Propriedades length :: [a] -> Int length [] = 0 length (x:xs) = 1 + (length xs) null :: [a] -> Bool null [] = True null _ = False 127
Sublistas lista = [1..10] take :: Int -> [a] -> [a] take 0 xs = [] take n [] = [] take n (x:xs) = x : (take (n-1) xs) take 2 lista -- [1,2] 128
Strings como Lista Uma String em Haskell pode ser representada como uma lista do tipo Char: type String = [Char] palavra = "Ola Mundo" primeiraletra :: String -> Char primeiraletra (w:ws) = w primeiraletra palavra -- O 129
Trabalhando com Listas Suponha as listas: listanum = [5, 3, 1, 2, 4] listastr = ["cachorro", "gato", "arara"] 130
Trabalhando com Listas Digamos que queremos triplicar os números da primeira lista e colocar uma exclamação no final de cada string. Poderíamos fazer: listanum = [5, 3, 1, 2, 4] listatriplo = [3*x x <- listanum] addexc (x:[]) = x : [! ] addexc (x:xs) = x : addexc xs listastr = ["cachorro", "gato", "arara"] listastre = [addexc s s <- listastr] 131
Trabalhando com Listas O padrão: [f x x <- lista] é recorrente em muitos algoritmos, podemos criar uma função genérica map que sabe aplicar uma função f em qualquer tipo x. 132
Trabalhando com Listas A função map recebe uma função que transforma um tipo a no tipo b, uma lista do tipo a e retorna uma lista do tipo b. Note que a e b podem ser iguais. map :: (a -> b) -> [a] -> [b] map f xs = [f x x <- xs] triplica x = 3*x listatriplo = map triplica listanum listastre = map addexc listastr 133
Trabalhando com Listas Digamos agora que queremos remover todos os valores ímpares de listatriplo: listatriplopar = [x x <- listatriplo, even x] E também todos menores ou iguais a 4: listatriplopar = [x x <- listatriplo, even x, x > 4] Esse também é um padrão recorrente que podemos deixar mais claro utilizando a função filter. 134
Trabalhando com Listas A função filter, recebe uma função que avalia um tipo a em um booleano, uma lista do tipo a e retorna uma lista do mesmo tipo, filtrada pelo predicado. filter :: (a -> Bool) -> [a] -> [a] filter f xs = [x x <- xs, f x] maiorquatro x = x > 4 listafinal = filter maiorquatro $ filter even $ map triplica listanum Com isso conseguimos representar as operações como um fluxo de dados, os elementos de listanum passa pela função triplica para depois passar pelo crivo de even e, finalmente, por maiorquatro. 135
Funções anônimas Quando as funções a serem passadas como parâmetro são pequenas, podemos utilizar as funções anônimas: map (\x -> 3*x) listanum filter \x -> x > 4) listatriplo A sintaxe \ representa o símbolo λ, então devemos ler como função λ que recebe um parâmetro x e retorna ( ) uma expressão em função de x. Note que as funções anônimas podem ter apenas uma única expressão simples. 136
Pointfree notation Adicionalmente, quando a função já está definida ou é um operador podemos utilizar o estilo pointfree: map (3*) listanum map (*3) listanum filter (>4) listatriplo 137
Exercício Dada a seguinte função: collatz :: Integer -> Integer collatz n even n = n div 2 otherwise = 3*n + 1 Gere uma lista com os elementos ímpares da aplicação dessa função nos números naturais. 138
Exercício Dada a seguinte função: collatz :: Integer -> Integer collatz n even n = n div 2 otherwise = 3*n + 1 lista = filter odd $ map collatz [1..] Gere uma lista com os elementos ímpares da aplicação dessa função nos números naturais. 139
Trabalhando com Listas Agora queremos somar os valores de listafinal, para isso podemos criar uma função: sum :: Num a => [a] -> a 140
Trabalhando com Listas Agora queremos somar os valores de qtdebrinquedos, para isso podemos criar uma função: sum :: Num a => [a] -> a sum [] = 0 sum (x:xs) = x + (sum xs) total = sum listafinal 141
Trabalhando com Listas A função sum pode ser generalizada para qualquer tipo de operação. Digamos que queremos calcular a produtória de uma lista. Como podemos alterar sum? sum :: Num a => [a] -> a sum [] = 0 sum (x:xs) = x + (sum xs) prod :: Num a => [a] -> a 142
Trabalhando com Listas A função sum pode ser generalizada para qualquer tipo de operação. Digamos que queremos calcular a produtória de uma lista. Como podemos alterar sum? sum :: Num a => [a] -> a sum [] = 0 sum (x:xs) = x + (sum xs) prod :: Num a => [a] -> a prod [] = 1 pro (x:xs) = x * (prod xs) Basicamente alteramos o valor neutro de 0 parar 1 e o operador aplicado no caso genérico. 143
Folding Esse tipo de operação é conhecido como fold ou reduce dependendo da linguagem funcional. Basicamente essa operação parte de um elemento neutro e aplica uma operação de redução sequencialmente entre o acumulador atual e o próximo elemento da lista: foldr :: (a -> b -> a) -> a -> [b] -> a foldr f z [] = z foldr f z (x:xs) = f x (foldr f z xs) Uma função que agrega o resultado de um tipo a com um tipo b, um elemento neutro a e uma lista de b, resultando em a. 144
Folding Alternativamente podemos definir uma versão caudal: foldl :: (a -> b -> a) -> a -> [b] -> a foldl f z [] = z foldl f z (x:xs) = foldl f (f z x) xs 145
Folding Nossas funções soma e produtoria podem ser definidas como: sum = foldl (+) 0 produtoria = foldl (*) 1 146
Exercício Dada a definição do operador &&: (&&) False _ = False (&&) _ False = False (&&) = True Expanda as seguintes expressões: foldl (&&) False [False, False, False, False] foldr (&&) False [False, False, False, False] 147
Exercício foldl (&&) (False && False) [False, False, False] foldl (&&) ((False && False) && False) [False, False] foldl (&&) (((False && False) && False) && False) [False] foldl (&&) (((False && False) && False) && False) && False (((False && False) && False) && False) && False False 148
Exercício False && (foldr (&&) False [False, False, False]) False 149
Trabalhando com Listas Finalmente, temos as funções sort e nub que ordena uma lista e gera apenas os elementos únicos, respectivamente: sort :: Ord a => [a] -> [a] nub :: Eq a => [a] -> [a] 150
Algebraic Data Type
Algebraic Data Types Tipos compostos de dados primitivos. Permite expressividade. 151
Tipos de Dados Algébricos Tipo soma: data Bool = True False data: declara que é um novo tipo Bool: nome do tipo True False: poder assumir ou True ou False 152
Tipos de Dados Algébricos Tipo produto: data Ponto = Ponto Double Double data: declara que é um novo tipo Ponto: nome do tipo Ponto: construtor (ou envelope) Double Double: tipos que ele encapsula 153
Tipos de Dados Algébricos Para ser possível imprimir esse tipo: data Ponto = Ponto Double Double deriving (Show) deriving: derivado de outra classe Show: tipo imprimível Isso faz com que o Haskell crie automaticamente uma instância da função show para esse tipo de dado. 154
Convertendo entre tipos e String A função read lê uma String e converte para um tipo específico, a função show converte um tipo para uma String. x = read "2" :: Integer y = read "2.3" :: Double z = read "True" :: Bool sx = show x sy = show y sz = show z 155
Tipos de Classe - deriving Além do Show temos os seguintes tipos de classe que podem ser derivadas: Read: que podem ser lidas a partir de uma String Eq: que possuem relação de igualdade Ord: possuem relação de ordem Enum: que são enumeráveis Bounded: possuem um limite inferior e superior Caso o compilador não consiga criar as funções pertinentes automaticamente, será necessário implementá-las. 156
Tipos de Classe - deriving Outras duas funções importantes para conversão: fromintegral: converte de um inteiro para um tipo da classe Num, pertinente para a expressão a ser calculada fromenum: converte para a posição de um tipo enumerável. 157
Tipos de Dados Algébricos data Ponto = Ponto Double Double deriving (Show) main = do print (Ponto 12.2 1.1) Ponto 12.2 1.1 158
Tipos de Dados Algébricos data Ponto = Ponto Double Double deriving (Show) dist :: Ponto -> Ponto -> Double dist (Ponto x1 y1) (Ponto x2 y2) = sqrt $ (x1-x2)^2 + (y1-y2)^2 159
Tipos de Dados Algébricos data Circ = Circ Ponto Double deriving (Show) Quero mais declarativo! 160
Tipos de Dados Algébricos type Centro = Ponto type Raio = Double data Circ = Circ Centro Raio deriving (Show) type é um apelido para um tipo! 161
Tipos de Dados Algébricos data Dias = Seg Ter Qua Qui Sex Sab Dom deriving (Show, Enum) Enum é enumerativo: succ Seg == Ter pred Ter == Seg 162
Tipos de Dados Algébricos data Dias = Seg Ter Qua Qui Sex Sab Dom deriving (Show, Enum, Eq) Eq é comparativo de igualdade: Seg == Ter -- False Seg == Seg -- True 163
Tipos de Dados Algébricos data Dias = Seg Ter Qua Qui Sex Sab Dom deriving (Show, Enum, Ord) Ord é relacional: Seg < Ter Ter > Seg -- True -- False 164
Tipos de Dados Algébricos data List a = Nil Cons a (List a) Uma lista de tipo a é definido ou como vazio (Nil) ou como a constante do tipo a e uma lista do tipo a. [1,2,3] = Cons 1 (Cons 2 (Cons 3 Nil)) 165
Tipos de Dados Algébricos data Maybe a = Nothing Just a 166
Tipos de Dados Algébricos safediv :: Double -> Double -> Maybe Double safediv x y y==0 = Nothing otherwise = Just (x / y) media l = case safediv (sum l) (fromintegral $ length l) of Nothing -> 1/0 -- infinity Just m -> m 167
Atividade 01 Faça os exercícios das páginas Exercícios Básicos, Exercícios sobre Funções e Exercícios sobre Listas da seguinte página: Curso de Haskell 168