Paradigma funcional

2,211 views

Published on

Published in: Education
3 Comments
0 Likes
Statistics
Notes
  • @thild

    Não mencionei isso antes, mas é realmente muito bom que o departamento tenha incluído outros paradigmas de programação na grade curricular, pena que nós não tivemos a mesma oportunidade durante o curso.

    Espero que os novos alunos se interessem de fato em aprender/explorar o paradigma funcional, principalmente agora que a indústria está reconhecendo o seu valor.

    Parabéns pelo trabalho Tony.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Opa Juninho, obrigado pelas considerações. Eu vou falar a respeito da imutabilidade, mapeamento, monads e efeitos colaterais nas próximas aulas. Abraço
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Tony,

    No slide 5, creio que você pode explicitar que linguagens não funcionais encorajam o uso de estado mutável por meio da atribuição de variáveis. Não sei até que extensão você pretende destacar os problemas associados com estado mutável e efeitos colaterais.

    No slide 6, creio que seja interessante destacar que linguagens funcionais avaliam expressões e essas sempre possuem um retorno, mesmo que void ex: print : 'a -> unit.

    No slide 13 você poderia alterar o texto da função Remove para clarificar que ela não altera a lista de entrada, mas sim que cria uma nova lista - isso apenas para reforçar a imutabilidade

    Talvez você possa incluir algo para deixar claro que na matemática uma função nunca altera um valor ou mesmo cria um novo valor.
    Uma função apenas mapeia objetos de um domínio para um contra-domínio (podendo ser do mesmo tipo). Por exemplo uma função square : int -> int apenas mapeia um int (valor que já existe no domínio) para um outro int (que também já existe no contra-domínio) mas nesse caso o domínio e contra-domínio são iguais.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Be the first to like this

No Downloads
Views
Total views
2,211
On SlideShare
0
From Embeds
0
Number of Embeds
1,098
Actions
Shares
0
Downloads
36
Comments
3
Likes
0
Embeds 0
No embeds

No notes for slide
  • n = a `div` length xs
    where
    a = 10
    xs = [1,2,3,4,5]
  • Paradigma funcional

    1. 1. Paradigma Funcional Paradigmas de Linguagem de Programação 3 CC – Unicentro – 2014 Prof. Tony Alexander Hild
    2. 2. 2 A Crise do Software ● Como diminuir o tamanho e complexidade dos software modernos? ● Como reduzir o tempo e custo desenvolvimento de software? ● Como aumentar a confiança que os software finalizados irão funcionar corretamente?
    3. 3. 3 Solução para a crise ● Uma abordagem para solucionar a crise de software foi projetar novas linguagens de programação que: – Permitissem que programas fossem escritos de forma clara, concisa e com um alto nível de abstração; – Suportassem componentes de software reutilizáveis; – Encorajassem o uso de verificação formal; – Permitissem prototipagem rápida; – Fornecessem ferramentas poderosas para a solução de problemas. ● Linguagens funcionais fornecem um arcabouço particularmente elegante para abordar estas metas.
    4. 4. 4 O que é uma Linguagem Funcional? ● De uma maneira geral: – Programação funcional é um estilo de programação onde o método básico de programação é a aplicação de funções em argumentos; – Uma linguagem funcional é aquela que suporta e encoraja o estilo funcional;
    5. 5. 5 Exemplo ● Somando inteiros de 1 a 10 em C#: var total = 0; for (int i = 0; i < 10; i++) { total += i; } ● O método de computação é atribuição de variáveis.
    6. 6. 6 Exemplo ● Somando inteiros de 1 a 10 em Haskell: sum [1..10] ● O método de computação é aplicação de função.
    7. 7. 7 Histórico ● 1930: – Alonzo Church desenvolveu o cálculo lambda (cálculo-λ): ● Uma teoria de função simples mas poderosa. ● 1950: – John McCarthy desenvolve a Lisp, a primeira linguagem funcional, com algumas influências do cálculo lambda, mas mantendo as atribuições de variáveis.
    8. 8. 8 Histórico ● 1960: – Peter Landin desenvolveu a linguagem ISWIM (If you see what i mean), a primeira linguagem funcional pura, fortemente baseada no cálculo lambda, sem atribuições. ● 1970: – John Backus desenvolveu FP, uma linguagem funcional que enfatiza funções de ordem superior e raciocínio sobre programas.
    9. 9. 9 Histórico ● 1970: – Robin Milner e outros criaram ML, a primeira linguagem funcional moderna, qual introduziu inferência de tipos e tipos polimórficos (generics); – ML foi concebida como uma linguagem de script para realizar provas formais. ● 1970-1980: – David Turner desenvolveu uma série de linguagens funcionais preguiçosas (lazy), culminando no sistema Miranda.
    10. 10. 10 Histórico ● 1987: – Um comitê internacional de pesquisadores iniciou o desenvolvimento de Haskell, uma linguagem funcional lazy padronizada; – http://www.haskell.org/h ugs/
    11. 11. 11 Histórico ● 2003: – O comitê publica o relatório Haskell 98, definindo uma definição estável da linguagem; – http://www.haskell.org/o nlinereport/
    12. 12. 12 Haskell - Exemplo f [] = [] f (x:xs) = f ys ++ [x] ++ f zs where ys = [ a | a <- xs, a <= x ] zs = [ b | b <- xs, b > x ] ?
    13. 13. 13 Hugs ● Implementação do Haskell 98, e é o sistema Haskell mais utilizado; ● A natureza interativa do Hugs o torna ideal para os propósitos de ensino e prototipagem; ● Disponível em: http://www.haskell.org/hugs/
    14. 14. 14 Iniciando o Hugs ● Em um sistema Unix, o Hugs pode ser iniciado a partir do prompt de comando simplesmente escrevendo-se: # hugs __ __ __ __ ____ ___ _________________________________________ || || || || || || ||__ Hugs 98: Based on the Haskell 98 standard ||___|| ||__|| ||__|| __|| Copyright (c) 1994-2005 ||---|| ___|| World Wide Web: http://haskell.org/hugs || || Bugs: http://hackage.haskell.org/trac/hugs || || Version: September 2006 _________________________________________ Haskell 98 mode: Restart with command line option -98 to enable extensions Type :? for help Hugs>
    15. 15. 15 Iniciando o Hugs (2) ● Hugs> significa que o sistema Hugs está pronto para avaliar uma expressão; ● Por exemplo: Hugs> 2+3*4 14 Hugs> (2+3)*4 20 Hugs> sqrt (3^2 + 4^2) 5.0
    16. 16. 16 O Prelude padrão ● Haskell contém uma biblioteca chamada Prelude.hs que contém um grande número de funções: – Funções numéricas: +, *; – Funções em listas.
    17. 17. 17 Funções em listas ● Seleciona o primeiro elemento de uma lista: > head [1,2,3,4,5] 1 ● Remove o primeiro elemento de uma lista: > tail [1,2,3,4,5] [2,3,4,5] ● Seleciona o enésimo elemento de uma lista: > [1,2,3,4,5] !! 2 3 ● Seleciona os primeiros n elementos de uma lista: > take 3 [1,2,3,4,5] [1,2,3]
    18. 18. 18 Funções em listas (2) ● Remove os primeiros n elementos de uma lista: > drop 3 [1,2,3,4,5] [4,5] ● Calcula o comprimento de uma lista: > length [1,2,3,4,5] 5 ● Calcula a soma dos elementos de uma lista: > sum [1,2,3,4,5] 15
    19. 19. 19 Funções em listas (3) ● Calcula o produto dos elementos de uma lista: > product [1,2,3,4,5] 120 ● Junta duas listas: > [1,2,3] ++ [4,5] [1,2,3,4,5] ● Reverte uma lista: > reverse [1,2,3,4,5] [5,4,3,2,1]
    20. 20. 20 Aplicação de funções ● Na matemática, a aplicação de uma função é denotada usando parêntesis, e a multiplicação é geralmente denotada usando justaposição ou espaço. – f(a,b) + c d Aplica a função f à a e b, e adiciona o resultado Aplica a função f à a e b, e adiciona o resultado o produto ao produto de c e d. o produto ao produto de c e d.
    21. 21. 21 Aplicação de funções (2) ● Em Haskell, a aplicação de funções é denotada usando espaço, e a multiplicação é denotada usando *: – f a b + c*d Como no exemplo anterior, só que agora na Como no exemplo anterior, só que agora na sintaxe Haskell sintaxe Haskell
    22. 22. 22 Aplicação de funções (2) ● Também, a aplicação de funções tem precedência sobre todos os outros operadores: – f a + b Significa Significa ((ff aa)) ++ bb,, aaoo ccoonnttrráárriioo ddee ff ((aa ++ bb))
    23. 23. 23 Aplicação de funções (Exemplos) Matemática Haskell f(x) f x f(x,y) f x y f(g(x)) f (g x) f(x,g(y)) f x (g y) f(x)g(y) f x * g y
    24. 24. 24 Scripts em Haskell ● Apesar de haver funções na biblioteca padrão (prelude), é possível definir funções próprias; ● Novas funções são definidas em um script, um arquivo de texto contendo uma sequência de definições; ● Por convenção, scripts Haskell possuem extensão .hs.
    25. 25. 25 Meu primeiro script ● É útil manter duas janelas abertas ao programar em Haskell. Uma para o script o outra para o Hugs; ● Abra um editor, digite as duas expressões e salve com o nome teste.hs: double x = x + x quadruple x = double (double x)
    26. 26. 26 Meu primeiro script (2) ● Deixe o editor aberto e inicie o Hugs com: > hugs teste.hs ou > hugs > :load teste.hs
    27. 27. 27 Meu primeiro script (3) ● Agora Prelude.hs e teste.hs estão carregados, e as funções dos dois scripts podem ser usadas: > quadruple 10 40 > take (double 2) [1,2,3,4,5,6] [1,2,3,4]
    28. 28. 28 Meu primeiro script (4) ● Mantendo o Hugs aberto, retorne ao editor, adicione as definições abaixo, salve e digite :reload no Hugs: factorial n = product [1..n] average ns = sum ns `div` length ns ● Nota: – ● div é cercado por aspas invertidas (crase); ● x `f` y é apenas um syntactic sugar para f x y. ● div é cercado por aspas invertidas (crase); ● x `f` y é apenas um syntactic sugar para f x y.
    29. 29. 29 Meu primeiro script (5) > factorial 10 3628800 > average [1,2,3,4,5] [1,2,3]
    30. 30. 30 Requisitos para nomes ● Nomes de funções e argumentos devem iniciar em caixa baixa: myFun fun1 arg_2 x' ● Por convensão, argumentos de lista geralmente possuem o sufixo s nos seus nomes (notação húngara): xs ns nss
    31. 31. 31 A regra do layout a = 10 b = 20 c = 30 a = 10 b = 20 c = 30 a = 10 b = 20 c = 30
    32. 32. 32 A regra do layout (2) a = b + c where b = 1 C = 2 d = a * 2 a = b + c where {b = 1 c = 2} d = a * 2 Mesmo que AAggrruuppaammeenntoto i mimpplílcícitioto AAggrruuppaammeenntoto e exxpplílcícitioto
    33. 33. 33 Exercícios ● 1. Teste os slides 17-19 e 25-29 usando Hugs; ● 2. Corrija os erros do programa abaixo, e teste sua solução usando Hugs. N = a 'div' lenght xs where a = 10 xs = [1,2,3,4,5]
    34. 34. 34 Exercícios 3.Tente reescrever a função last, que seleciona o último elemento de uma lista, usando as funções vistas anteriormente; 4.Você consegue pensar em outra solução? 3.Similarmente, reescreva a função init, que remove o último elemento de uma lista.
    35. 35. 35 O que é um tipo? Um tipo é um nome para uma coleção de valores relacionados. Por exemplo, o tipo básico Bool contém dois valores lógicos: False True
    36. 36. 36 Erros de tipo ● Aplicar um função à um ou mais argumentos do tipo errado é chamado erro de tipo. > 1 + False Error 1 é um número e False é um valor lógico, mas 1 é um número e False é um valor lógico, mas + requer dois números + requer dois números
    37. 37. 37 Tipos em Haskell ● Se ao avaliar uma expressão e produzir um valor do tipo t, então e tem o tipo t, escrito: e :: t ● Toda expressão bem formada tem um tipo, que pode ser automaticamente calculado em tempo de compilação usando um processo chamado inferência de tipo.
    38. 38. 38 Tipos em Haskell (2) ● Todos os erros de tipo são verificados em tempo de compilação, o que torna os programas seguros e rápidos, removendo a necessidade de checagem de tipo em tempo de execução; ● No Hugs, o comando :type calcula o tipo de uma expressão, sem avaliá-la. > not False True > :type not False not False :: Bool
    39. 39. 39 Tipos básicos em Haskell Bool Valores lógicos Char Caracteres únicos String String (fio) de caracteres Int Inteiros de precisão fixa Integer Inteiros de precisão arbitrária Float Número de ponto flutuante
    40. 40. 40 Tipos Lista ● Uma lista é uma sequência de valores do mesmo tipo: [False,True,False] :: [Bool] ['a','b','c','d'] :: [Char] ● De maneria geral: [t] é o tipo de listas de elementos do tipo t.
    41. 41. 41 Tipos Lista (2) ● Nota: – O tipo das listas não diz nada sobre o seu tamanho [False,True] :: [Bool] [False,True,False] :: [Bool] – O tipo dos elementos não tem restrições. Por exemplo, é possível criar listas de listas: [['a'],['b','c']] :: [[Char]]
    42. 42. 42 Tipos Tupla ● Uma tupla é uma sequência de valores de tipos diferentes: (False,True) :: (Bool,Bool) (False,'a',True) :: (Bool,Char,Bool) ● De modo geral: – (t1,t2,...,tn) é o tipo de n-tuplas quais os iésimos componentes têm o tipo ti para qualquer i em 1..n.
    43. 43. 43 Tipos Tupla (2) ● Nota: – O tipo de uma tupla codifica seu tamanho: (False,True) :: (Bool,Bool) (False,True,False) :: (Bool,Bool,Bool) – Não há restrições para o tipo dos componentes: ('a',(False,'b')) :: (Char,(Bool,Char)) (True,['a','b']) :: (Bool,[Char])
    44. 44. 44 Tipos Função ● Uma função é um mapeamento de valores de um tipo para valores de outro tipo: not :: Bool → Bool isDigit :: Char → Bool ● De maneira geral: – t1 → t2 é o tipo das funções que mapeia valores do tipo t1 para valores do tipo t2.
    45. 45. 45 Tipos Função (2) ● Nota: – A flecha → é digitada no teclado como ->; – Não há restrições para o tipo de argumento e resultados. Por exemplo, funções com múltiplos argumentos ou resultados são possíveis usando listas ou tuplas. add :: (Int,Int) → Int add (x,y) = x + y zeroto :: Int → [Int] zeroto n = [0..n]
    46. 46. 46 Funções Arranjadas (Curried) ● Funções com múltiplos argumentos que retornam funções como resultado: add' :: Int → (Int → Int) add' x y :: x + y add' toma um valor inteiro x e retorna uma função add' x. Por sua vez, esta função toma um inteiro y add' toma um valor inteiro x e retorna uma função add' x. Por sua vez, esta função toma um inteiro y e retorna o resultado de x + y. e retorna o resultado de x + y.
    47. 47. 47 Funções Arranjadas (Curried) (2) ● Nota: – add e add' produzem o mesmo resultado final, mas add toma seus dois argumentos ao mesmo tempo, enquanto add' toma um de cada vez: add :: (Int,Int) → Int add' :: Int → (Int → Int) ● Funções que tomam um argumento por vez são chamadas funções arranjadas ou curried, em homenagem a Haskell Curry.
    48. 48. 48 Funções Arranjadas (Curried) (3) ● Funções com mais de dois argumentos podem ser arranjadas retornando funções aninhadas: mult :: Int → (Int → (Int → Int)) mult x y z = x*y*z mult toma um inteiro x e retorna uma função mult x, que por sua vez toma um inteiro y e retorna uma função mult mult toma um inteiro x e retorna uma função mult x, que por sua vez toma um inteiro y e retorna uma função mult x y, que finalmente toma um inteiro z e retorna o x y, que finalmente toma um inteiro z e retorna o resultado x * y * z. resultado x * y * z.
    49. 49. 49 Por que Currying é útil? ● Funções curried são mais flexíveis que funções em tuplas, porque funções úteis muitas vezes podem ser construídas aplicando parcialmente uma função curried. ● Por exemplo: add' 1 :: Int → Int take 5 :: [Int] → [Int] drop 5 :: [Int] → [Int]
    50. 50. 50 Convensões de curring ● Para evitar excesso de parênteses quando usa-se funções curried, duas convenções simples são adotadas: – A flecha → associa à direita. Int → Int → Int → Int SSiiggnniiffiiccaa IInntt →→ ((IInntt →→ ((IInntt →→ IInntt))))
    51. 51. 51 Convensões de curring (2) ● Por consequência, é natural aplicar uma função associando a esquerda. mult x y z SSiiggnniifficicaa ((((mmuultlt xx)) yy)) zz ● A não ser que tuplas sejam requeridas, todas as funções em Haskell são normalmente definidas na forma curried.
    52. 52. 52 Funções Polimórficas ● Uma função é chamada polimórfica (“de muitas formas”) se seu tipo contém uma ou mais variáveis de tipo. length :: [a] → Int Para qualquer tipo a, length toma uma lista de valores do tipo a e retorna um inteiro. Para qualquer tipo a, length toma uma lista de valores do tipo a e retorna um inteiro.
    53. 53. 53 Funções Polimórficas (2) ● Nota: – Variáveis de tipo podem ser instanciadas para diferentes tipos em diferentes circunstâncias: > length [False,True] 2 > length [1,2,3,4] 4 aa == BBooooll aa == IInntt – Variáveis de tipo devem começar com letras minúsculas, e usualmente são nomeadas a, b, c, etc.
    54. 54. 54 Funções Polimórficas (3) ● Várias funções do prelude são polimórficas: fst :: (a,b) → a head :: [a] → a take :: Int → [a] → [a] zip :: [a] → [b] → [(a,b)] id :: a → a
    55. 55. Char não é um tipo numérico 55 Funções Polimórficas (4) ● Variáveis de tipo restrito podem ser instanciadas para qualquer tipo que satisfaça a restrição. > sum [1,2,3] 6 > sum [1.1,2.2,3.3] 6.6 > sum ['a','b','c'] ERROR aa == IInntt aa == FFllooaatt Char não é um tipo numérico
    56. 56. 56 Funções Polimórficas (5) ● Haskell possui várias classes de tipo, incluindo: – Num – Tipos numéricos – Eq – Tipos de igualdade – Ord – Tipos de relação (ordenação) ● Por exemplo: (+) :: Num a => a → a → a (==) :: Eq a => a → a → Bool (<) :: Ord a => a → a → Bool
    57. 57. 57 Dicas e Sugestões ● Ao definir uma nova função em Haskell, é útil iniciar escrevendo seu tipo; ● Em um script, é uma boa prática definir o tipo de todas as novas funções; ● Quando definir os tipos de funções polimórficas que utilizam números, igualdades ou relações, tenha cuidado em incluir as classes de restrições necessárias.
    58. 58. 58 Dicas e Sugestões add :: Num a => a -> a -> a add x y = x + y
    59. 59. 59 Exercícios ● Quais são os tipos dos seguintes valores? ['a','b','c'] ('a','b','c') [(False,'0'),(True,'1')] [(False,True),('0','1')] [tail,init,reverse]
    60. 60. 60 Exercícios ● Quais são os tipos das seguintes funções? second xs = head (tail xs) swap (x,y) = (y,x) pair x y = (x,y) double x = x*2 palindrome xs = reverse xs == xs twice f x = f(f x) ● Verifique suas respostas usando o Hugs.
    61. 61. 61 Expressões Condicionais ● Na maioria das linguagens de programação, funções podem ser definidas usando expressões condicionais. abs :: Int → Int abs n = if n >= 0 then n else -n abs toma um inteiro n se n for não-negativo, abs toma um inteiro n se n for não-negativo, senão, retorna -n. senão, retorna -n.
    62. 62. 62 Expressões Condicionais ● Expressões condicionais podem ser aninhadas: signum :: Int → Int signum n = if n < 0 then -1 else ● Nota: if n == 0 then 0 else 1 – Em Haskell, expressões condicionais sempre devem ter o desvio senão (else), que evita ambiguidades com expressões aninhadas.
    63. 63. 63 Equações restritas (Guarded Equations) ● Com alternativa para expressões, funções também podem ser definidas usado equações restritas: abs n | n >= 0 = n | otherwise = -n Como o anterior, m Como o anterior, maass uussaannddoo gguuaarrddeedd..
    64. 64. 64 Equações restritas (Guarded Equations) ● Equações restritas podem ser usadas para simplificar a leitura de definições que envolvem múltiplas condições: ● Nota: signum n | n < 0 = -1 | n == 0 = 0 | otherwise = 1 – A condição otherwise é definida no prelude por otherwise = True.
    65. 65. 65 Pattern Matching ● Muitas funções possuem uma definição mais clara utilizando pattern matching (casamento de padrões) nos seus argumentos: not :: Bool → Bool not False = True not True = False not mapeia False para not mapeia False para TTrruuee,, ee TTrruuee ppaarraa FFaallssee
    66. 66. 66 Pattern Matching (2) (&&) :: Bool → Bool → Bool True && True = True _ && _ = False WWiillddccaarrdd (&&) :: Bool → Bool → Bool True && x = True , x == True = False , x == False False && x = False , x == False = False , x == True
    67. 67. 67 Pattern Matching (3) (&&) :: Bool → Bool → Bool True && True = True True && False = False False && True = False False && False = False (&&) :: Bool → Bool → Bool True && b = b False && _ = False
    68. 68. 68 Pattern Matching (4) ● Padrões são casados segundo a ordem top → bottom, left → rigth. Por exemplo, a seguinte definição sempre retorna False: _ && _ = False True && True = True ● Padrões não podem repetir variáveis. Por exemplo, a seguinte definição causa um erro: b && b = b _ && _ = False
    69. 69. 69 Padrões de Listas ● Internamente, toda lista não-vazia é construída pelo uso repetido do operador (:) chamado “cons” que adiciona um elemento ao início da lista. [1,2,3,4] SSiiggnniiffiiccaa 11::((22::((33::((44::[[]]))))))
    70. 70. 70 Padrões de Listas ● Funções em listas podem ser definidas utilizando padrões x:xs. head :: [a] → a head (x:_) = x tail :: [a] → [a] tail (_:xs) :: xs head e tail mapeia qualquer lista não-vazia para head e tail mapeia qualquer lista não-vazia para seus primeiros e últimos elementos seus primeiros e últimos elementos
    71. 71. 71 Padrões de Listas ● Nota: – O padrão x:xs somente casa listas não-vazias: > head [] Error – Padrões x:xs devem ser parentesados, por que a aplicação do padrão tem prioridade sobre (:). Por exemplo, a seguinte definição retorna um erro: head x:_ = x
    72. 72. 72 Padrões de Inteiros ● Como na matemática, funções em inteiros podem ser definidas usando o padrão n+k, onde n é uma variável inteira e k>0 é uma constante inteira. pred :: Int → Int pred (n+1) = n pred mapeia qualquer inteiro positivo para seu predecessor pred mapeia qualquer inteiro positivo para seu predecessor
    73. 73. 73 Padrões de Inteiros fac :: Int → Int fac 0 = 1 fac (n+1) = n + (fac n)
    74. 74. 74 Padrões de Inteiros ● Nota: – Padrões n+k casam somente inteiros ≥ k. > pred 0 Error – Padrões n+k devem ser parentesados, porque a aplicação tem prioridade sobre +. Por exemplo, a seguinte definição resulta em um erro: pred n+1 = n
    75. 75. 75 Expressões Lambda ● Funções podem ser construídas sem serem nomeadas usado expressões lambda. λx → x + x uma função sem nome que toma um número x e retorna o uma função sem nome que toma um número x e retorna o resultado x+x resultado x+x
    76. 76. 76 Expressões Lambda ● Em Haskell: Int -> Int x -> x + x
    77. 77. 77 Expressões Lambda ● Nota: – O símbolo λ é a letra Grega lambda, e é escrita no teclado com ; – Na matemática, funções sem nome são usualmente denotadas usado o símbolo ↦, como em x ↦ x + x; – Em Haskell, o uso do símbolo λ para funções sem nome vem do cálculo lambda, a teoria de funções no qual Haskell é baseado.
    78. 78. 78 Por que Lambdas são úteis? Expressões lambda poder ser usadas para dar um significado formal para funções definidas com currying. Por exemplo: significa add x y = x + y add = λx → (λy → x + y)
    79. 79. 79 Por que Lambdas são úteis? Expressões lambda também são úteis ao definir funções que retornam funções como resultado. Por exemplo: const :: a → b → a const x _ = x é mais naturalmente definido por const :: a → (b → a ) const x = λ_ x → x
    80. 80. 80 Por que Lambdas são úteis? Expressões lambda poder ser usadas para evitar dar nomes a funções que são referenciadas uma vez apenas. Por exemplo: odds n = map f [0..n-1] where f x = x*2 + 1 Pode ser simplificado para odds n = map (λx → x*2 + 1) [0..n-1]
    81. 81. 81 Seções ● Um operador escrito entre seus dois argumentos pode ser convertido em uma função curried escrita antes de seus dois argumentos usando parênteses. ● Por exemplo: > 1 + 2 3 > (+) 1 2 3
    82. 82. 82 Seções ● Esta convenção ainda permite que um dos argumentos do operador seja incluído nos parênteses. ● Por exemplo: > (1+) 2 3 > (+2) 1 3 ● Geralmente, se é ⊕ um operador, então funções na forma (⊕), (x⊕) e (⊕y) são chamadas seções.
    83. 83. 83 Por que seções são úteis? Funções úteis as vezes podem ser construídas de uma maneira simples utilizando seções. Por exemplo: (1+) – função sucessor (1/) – função recíproca (*2) – função para dobrar (/2) – função para dividir ao meio
    84. 84. 84 Exercícios 1.Considere a função safetail que tem o mesmo comportamento da função tail, exceto por safetail mapear lista vazia para lista vazia, sendo que tail retorna um erro neste caso. – Defina safetail usando: ● Uma expressão condicional; ● Uma expressão abrigada (guarded); ● Casamento de padrões. – Dica: a função null :: [a] → Bool pode ser usada para verificar se uma lista é vazia.
    85. 85. 85 Exercícios 2.Crie três possíveis definições para o operador lógica (||) usando casamento de padrões; 3.Redefina a seguinte versão de (&&) usando expressões condicionais ao invés de casamento de padrões: True && True = True _ && _ = False 6.Faça o mesmo para a seguinte versão: True && b = b False && _ = False
    86. 86. 86 Listas por Compreensão (List Comprehensions) ● Na matemática, a notação por compreensão pode ser usada para construir novos conjuntos a partir de conjuntos antigos. O conjunto {1,4,9,16,25} de todos os números x2 tal que x seja um elemento do conjunto {1...5} O conjunto {1,4,9,16,25} de todos os números x2 tal que x seja um elemento do conjunto {1...5}
    87. 87. 87 Listas por Compreensão (2) ● Em Haskell, um notação por compreensão similar pode ser usada para construir novas listas a partir de listas antigas. [x^2 | x ← [1..5]] A lista [1,4,9,16,25] de todos os números x^2 tal que x seja um elemento da lista [1..5] A lista [1,4,9,16,25] de todos os números x^2 tal que x seja um elemento da lista [1..5]
    88. 88. 88 Listas por Compreensão (3) ● Nota: – A expressão x ← [1..5] é chamada geradora, pois ela define como gerar os valores para x; – Compreensões podem ter várias geradoras, separadas por vírgula. Por exemplo: > [(x,y) | x ← [1,2,3], y ← [4,5]] [(1,4),(1,5),(2,4),(2,5),(3,4),(3,5)]
    89. 89. 89 Listas por Compreensão (4) ● Nota: – Alterando a ordem das geradoras altera a ordem dos elementos na lista final: > [(x,y) | x ← [4,5], y ← [1,2,3]] [(1,4),(2,4),(3,4),(1,5),(2,4),(3,5)] – Múltiplas geradoras são como laços aninhados, sendo as últimas geradoras são os laços mais profundos onde os valores das variáveis se alteram com mais frequência.
    90. 90. 90 Listas por Compreensão (5) ● Por exemplo: > [(x,y) | x ← [4,5], y ← [1,2,3]] [(1,4),(2,4),(3,4),(1,5),(2,4),(3,5)] x ← [1,2,3] é a última geradora, sendo assim x ← [1,2,3] é a última geradora, sendo assim o valor do componente x se altera com mais frequência o valor do componente x se altera com mais frequência
    91. 91. 91 Geradoras Dependentes ● As últimas expressões geradoras podem depender de variáveis introduzidas nas primeiras geradoras. [(x,y) | x ← [1..3], y ← [x..3]] A lista [(1,1),(1,2),(1,3),(2,2),(2,3),(3,3)] de todos os pares dos números (x,y) A lista [(1,1),(1,2),(1,3),(2,2),(2,3),(3,3)] de todos os pares dos números (x,y) tal que x,y sejam elementos da lista [1..3] e y >= x tal que x,y sejam elementos da lista [1..3] e y >= x
    92. 92. 92 Geradoras Dependentes ● Usando uma expressão geradora dependente pode-se definir uma função de biblioteca que concatena uma lista de listas. concat :: [[a]] → [a] concat xss = [x | xs ← xss, x ← xs] ● Por exemplo: concat [[1,2,3],[4,5],[6]] [1,2,3,4,5,6]
    93. 93. 93 Guards ● Listas por compreensão podem usar “guards” para restringir o valor produzido pelas primeiras geradoras. [x | x ← [1..10], even x] A lista [2,4,6,8,10] A lista [2,4,6,8,10] de todos os números x tal que x seja um elemento da lista [1..10] e x seja par de todos os números x tal que x seja um elemento da lista [1..10] e x seja par
    94. 94. [x | x <- [1..n], n `mod` x == 0] 94 Guards ● Usando “guards” é possível definir uma função que mapeia um número inteiro positivo para sua lista de fatores: factors :: Int -> [Int] factors n = ● Por exemplo: > factors 15 [1,3,5,15]
    95. 95. 95 Guards ● Um inteiro positivo é primo se seus únicos fatores forem 1 e ele mesmo. Sendo assim, usando factors é possível definir uma função que decide se um número é primo: prime :: Int → Bool prime n = factors n == [1,n] ● Por exemplo: > prime 15 False > prime 7 True
    96. 96. 96 Guards ● Agora, usando “guards” é possível definir uma função que retorna a lista de todos os primos até um dado limite: primes ::Int → [Int] primes n = [x | x ← [2..n], prime x] ● Por exemplo: > primes 40 [2,3,5,7,11,13,17,19,23,29,31,37]
    97. 97. 97 A função Zip ● Uma função de biblioteca muito útil, qual mapeia duas listas para uma lista de pares de seus elementos correspondentes. zip :: [a] → [b] → [(a,b)] ● Por exemplo: > zip ['a','b','c'][1,2,3,4] [('a',1),('b',2),('c',3)]
    98. 98. 98 A função Zip ● Usando zip é possível definir uma função que retorna uma lista de todos os pares de elementos adjacentes de outra lista: pairs :: [a] → [(a,a)] pairs xs = zip xs (tail xs) ● Por exemplo: > pairs [1,2,3,4] [(1,2),(2,3),(3,4)]
    99. 99. ● Usando pairs é possível definir uma função que decide se os elementos de uma lista estão ordenados: 99 sorted :: Ord a => [a] → Bool sorted xs = and [x <= y | (x,y) ← pairs xs] ● Por exemplo: > sorted [1,2,3,4] True > sorted [1,3,2,4] False
    100. 100. ● Usando zip é possível definir uma função que retorna uma lista de todas as posições de um valor em uma lista: 100 positions :: Eq a => a → [a] → [Int] positions x xs = [i | (x',i) ← zip xs [0..n], x == x'] where n = length xs - 1 ● Por exemplo: > positions 0 [1,0,0,1,0,1,1,0] [1,2,4,7]
    101. 101. 101 Compreensões em String ● Uma string é uma sequência de caracteres cercadas por aspas duplas. Internamente, no entanto, strings são representadas como listas de caracteres. “abc” :: String Significa Significa [['a'a',','b'b',','c'c']'] :::: [[CChhaarr]]
    102. 102. 102 ● Similarmente, compreensões em strings podem ser usadas para definir funções em strings como, por exemplo, uma função que conta as letras minúsculas (caixa-baixa) em uma string: lowers :: String → Int lowers xs = length [x | x ← xs, isLower x] ● Por exemplo: > lowers “Haskell” 6
    103. 103. 103 Exercícios 1. Um tripla (x,y,z) de inteiros positivos é chamada de pitagórica se x² + y² = z². Usando uma compreensão em lista, defina a função pyths :: Int → [(Int,Int,Int)] que mapeia um inteiro n para todas as triplas com componentes em [1..n]. Por exemplo: > pyths 5 [(3,4,5),(4,3,5)]
    104. 104. 104 Exercícios 2. Um inteiro positivo é perfeito se for igual à soma de todos os seus fatores, excluindo o próprio número. Usando uma compreensão de lista, defina uma função perfects :: Int → [Int] que retorne a lista de todos os números perfeitos até um dado limite. Por exemplo: > perfects 500 [6,28,496]
    105. 105. 105 Leitura Hughes, John. Why Functional Programming Matters http://www.cse.chalmers.se/~rjmh/Papers/whyfp.pdf
    106. 106. 106 Funções recursivas ● Como visto, muitas funções podem ser definidas em termos de outras funções. factorial :: Int → Int factorial n = product [1..n] factorial mapeia qualquer inteiro n para o produto dos factorial mapeia qualquer inteiro n para o produto dos inteiros entre 1 e n inteiros entre 1 e n
    107. 107. 107 Funções recursivas (2) ● Expressões são avaliadas em um processo passo-a-passo de aplicação de funções em seus argumentos. ● Por exemplo: factorial 4 = product [1..4] = product [1,2,3,4] = 1*2*3*4 = 24
    108. 108. 108 Funções recursivas (3) product :: [Int] → Int product [] = 1 product (x:xs) = x * product xs .... fact 4 [1,2,3,4] = product (1:(2:(3:(4:[])))) = 1 * product (2:(3:(4:[]))) . . .
    109. 109. 109 Funções recursivas (4) ● Em Haskell, funções podem ser definidas em termos delas mesmas. Tal funções são chamadas recursivas. factorial 0 = 1 factorial (n+1) = (n+1) * factorial n factorial mapeia 0 para 1, e qualquer outro inteiro positivo para o produto dele mesmo com o fatorial de factorial mapeia 0 para 1, e qualquer outro inteiro positivo para o produto dele mesmo com o fatorial de seu predecessor. seu predecessor.
    110. 110. 110 Funções recursivas (5) factorial 3 = 3 * factorial 2 = 3 * (2 * factorial 1) = 3 * (2 * (1 * factorial 0)) = 3 * (2 * (1 * 1)) = 3 * (2 * 1) = 3 * 2 = 6
    111. 111. 111 Funções recursivas (nota) ● factorial 0 = 1 é apropriado pois 1 é o valor identidade da multiplicação: 1*x = x = x*1 ● A definição da função recursiva diverge em inteiros < 0 porque o caso base nunca é alcançado: > factorial (-1) Error: Control stack overflow
    112. 112. 112 Porque recursão é útil ● Algumas funções, como factorial, são mais simples de definir em termos de outras funções. ● Como visto, no entanto, muitas funções podem ser naturalmente definidas em termos delas mesmas. ● Propriedades de funções definidas usando recursão podem ser provadas usando uma simples porém poderosa técnica matemática de indução.
    113. 113. 113 Recursões em listas ● Recursão não se restringe à números, mas também pode ser usado para definir funções em listas. product :: [Int] → Int product [] = 1 product (x:xs) = x * product xs product mapeia a lista vazia para 1, e qualquer outra lista não-vazia para sua cabeça multiplicada pelo product mapeia a lista vazia para 1, e qualquer outra lista não-vazia para sua cabeça multiplicada pelo produto de sua calda. produto de sua calda.
    114. 114. 114 Recursões em listas (2) product [2,3,4] = 2 * product [3,4] = 2 * (3 * product[4]) = 2 * (3 * ( * 4 * product[]) = 2 * (3 * (4 * 1)) = 24
    115. 115. 115 Recursões em listas (3) ● Usando o mesmo padrão de recursão como em product pode-se definir a função length em listas. length :: [a] → Int length [] = 0 length (_:xs) = 1 + length xs length mapeia um lista vazia para 0, e qualquer outra lista não-vazia para o sucessor do comprimento de length mapeia um lista vazia para 0, e qualquer outra lista não-vazia para o sucessor do comprimento de sua calda. sua calda.
    116. 116. 116 Recursões em listas (4) length [1,2,3] = 1 + length [2,3] = 1 + (1 + length [3]) = 1 + (1 + (1 + (length [])) = 1 + (1 + (1 + (0)) = 3
    117. 117. 117 Recursões em listas (5) ● Usando um padrão similar de recursão pode-se definir a função reverse em listas. reverse :: [a] → [a] reverse [] = [] reverse (x:xs) = reverse xs ++ [x] reverse mapeia um lista vazia outra lista vazia, e qualquer outra lista não-vazia para o reverso da sua reverse mapeia um lista vazia outra lista vazia, e qualquer outra lista não-vazia para o reverso da sua calda concatenada com a cabeça. calda concatenada com a cabeça.
    118. 118. 118 Recursões em listas (4) reverse [1,2,3] = reverse [2,3] ++ [1] = (reverse [3] ++ [2]) ++ [1] = ((reverse [] ++ [3] ++ [2]) ++ [1] = (([] ++ [3] ++ [2]) ++ [1] = [3,2,1]
    119. 119. 119 Múltiplos argumentos em recursão ● Funções com mais de um argumento também podem ser definidas usando recursão. ● Por exemplo, zipping os elementos de duas listas: zip :: [a] → [b] → [(a,b)] zip [] _ = [] zip _ [] = [] zip (x:xs) (y:ys) = (x,y) : zip xs ys
    120. 120. 120 Múltiplos argumentos em recursão (2) ● Remover os primeiros n elementos de uma lista: drop :: Int → [a] → [a] drop 0 xs = xs drop (n+1) [] = [] drop (n+1) (_:xs) = drop n xs ● Concatenar duas listas: (++) :: [a] → [a] → [a] [] ++ ys = ys (x:xs) ++ ys = x : (xs ++ ys)
    121. 121. 121 Quicksort ● O algoritmo quicksort para ordenar uma lista de inteiros pode ser especificado pelas duas regras seguintes: – Uma lista vazia já está ordenada; – Listas não-vazias podem ser ordenadas ordenando os valores da cauda <= à cabeça, ordenando os valores da cauda > que a cabeça, e concatenando as duas listas resultantes em cada lado do valor da cabeça.
    122. 122. 122 Quicksort (2) ● Usando recursão, esta especificação pode ser traduzida diretamente para uma implementação: qsort :: [Int] -> [Int] qsort [] = [] qsort (x:xs) = qsort menores ++ [x] ++ qsort maiores where menores = [a | a <- xs, a <= x] maiores = [b | b <- xs, b > x] Nota: Esta é provavelmente a implementação mais simples de quicksort em qualquer linguagem de programação. Nota: Esta é provavelmente a implementação mais simples de quicksort em qualquer linguagem de programação.
    123. 123. 123 Quicksort (3) qq [[33,,22,,44,,11,,55]] qq [[22,,11]] ++++ [[33]] ++++ qq [[44,,55]] qq [[11]] ++++ [[22]] ++++ qq [[]] qq [[]] ++++ [[44]] ++++ qq [[55]] [[11]] [[]] [[]] [[55]]
    124. 124. 124 Exercícios ● Sem olhar no prelude padrão, defina as seguinte biblioteca de funções usando recursão: – Verificar se todos os valores em uma lista são verdadeiros: and :: [Bool] → Bool – Concatenar uma lista de listas: concat :: [[a]] → [a] – Produzir uma lista com n elementos idênticos: replicate :: Int → a → [a] – Selecionar o iésimo elemento de uma lista: (!!) :: [a] → Int → a – Verificar se um valor é um elemento de da lista: elem :: Eq a => a → [a] → Bool
    125. 125. 125 Haskell Capítulo 7 – Funções de Alta Ordem (Funções de Ordem Superior)
    126. 126. 126 Introdução ● Uma função é chamada de alta ordem se ela toma uma função como argumento ou retorna uma função como resultado. twice :: (a → a) → a → a twice f x = f (f x) twice é uma função de alta ordem porque toma uma twice é uma função de alta ordem porque toma uma função como seu primeiro argumento função como seu primeiro argumento
    127. 127. 127 Porque são tão úteis? ● Idiomas comuns de programação podem ser codificados como funções dentro própria linguagem. ● Linguagens de domínio específico podem ser definidas como coleções de funções de alta ordem. ● Propriedades algébricas de funções de alta ordem podem ser usadas para raciocinar sobre programas.
    128. 128. 128 A função map ● A função de alta ordem da biblioteca chamada map aplica uma função para cada elemento de uma lista. map :: (a → b) → [a] → [b] ● Por exemplo: > map (+1) [1,3,5,7] [2,4,6,8]
    129. 129. 129 A função map (2) ● A função map pode ser definida em uma maneira particularmente simples usando compreensão de lista. map f xs = [f x | x ← xs] Alternativamente, para provar, a função map pode ser definida utilizando recursão: map f [] = [] map f (x:xs) = f x : map f xs
    130. 130. 130 A função filter ● A função de alta ordem filter seleciona todos os elementos de uma lista que satisfazem um predicado. filter :: (a → Bool) → [a] → [a] ● Por exemplo: > filter even [1..10] [2,4,6,8,10]
    131. 131. 131 A função filter (2) ● Filter pode ser definida utilizando compreensão de lista. filter p xs = [x | x ← xs, p x] Alternativamente, pode ser definida utilizando recursão: filter p [] = [] filter p (x:xs) | p x = x : filter p xs | otherwise = filter p xs
    132. 132. 132 A função foldr ● Várias funções em listas podem ser definidas utilizando o seguinte padrão simples: f [] = v f (x:xs) = x ⊕ f xs f mapeia um lista vazia para algum valor v, e qualquer outra lista não vazia para alguma uma função ⊕ aplicada na cabeça e f na calda f mapeia um lista vazia para algum valor v, e qualquer outra lista não vazia para alguma uma função ⊕ aplicada na cabeça e f na calda
    133. 133. 133 A função foldr (2) - Exemplo sum [] = 0 sum (x:xs) = x + sum xs v = 0 v = 0 ⊕ = + ⊕ = + product [] = 1 product (x:xs) = x * product xs v = 1 ⊕ = * v = 1 ⊕ = * and [] = True and (x:xs) = x && and xs v = True ⊕ = && v = True ⊕ = &&
    134. 134. 134 A função foldr (3) ● A função de alta ordem foldr (fold right) encapsula este padrão de recursão simples, juntamente com a função ⊕ e o valor v como argumentos. sum = foldr (+) 0 product = foldr (*) 1 or = foldr (||) False and = foldr (&&) True
    135. 135. 135 A função foldr (4) ● foldr pode ser definida usando recursão: foldr :: (a -> b -> b) -> b -> [a] -> b foldr f v [] = v foldr f v (x:xs) = f x (foldr f v xs) ● No entanto, é melhor pensar em foldr de forma não recursiva, simultaneamente substituindo cada (:) em uma lista para um função dada, e [] para um valor dado.
    136. 136. 136 A função foldr (5) sum [1,2,3] = foldr (+) 0 [1,2,3] = foldr (+) 0 (1:(2:(3:[]))) = 1+(2+(3+0)) = 6 Substitua cada (:) por (+) e [] por 0 Substitua cada (:) por (+) e [] por 0
    137. 137. 137 A função foldr (6) product [1,2,3] = foldr (*) 1 [1,2,3] = foldr (*) 1 (1:(2:(3:[]))) = 1*(2*(3*1)) = 6 Substitua cada (:) por (*) e [] por 1 Substitua cada (:) por (*) e [] por 1
    138. 138. 138 A função foldr (7) ● Mesmo foldr encapsulando um padrão de recursão simples, ela pode ser usada para definir muitas outras funções do que se possa esperar. ● Relembrando a função length: length :: [a] -> Int length [] = 0 length (_:xs) = 1 + length xs
    139. 139. Substitua cada (:) por λ_ n → 1+n e 139 A função foldr (8) length [1,2,3] = length (1:(2:(3:[]))) = 1+(1+(1+0)) = 3 Substitua cada (:) por λ_ n → 1+n e [] por 0 [] por 0 length = foldr (_ n -> 1+n) 0
    140. 140. Reverse [] = [] reverse (x:xs) = reverse xs ++ [x] Substitua cada (:) por λx xs → xs ++ 140 A função foldr (9) reverse [1,2,3] = reverse (1:(2:(3:[]))) = (([] ++ [3]) ++ [2]) ++ [1] = [3,2,1] Substitua cada (:) por λx xs → xs ++ [x] e [] por [] [x] e [] por [] ● Agora relembrando a função reverse:
    141. 141. Substitua cada (:) por (:) [] por ys 141 A função foldr (10) ● Portanto, nós temos: (++ ys) = foldr (:) ys Substitua cada (:) por (:) [] por ys reverse = foldr (x xs -> xs ++ [x]) [] ● Finalmente, nota-se que a função de concatenação (++) tem uma definição particularmente compacta utilizando foldr:
    142. 142. 142 Por que foldr é útil? ● Algumas funções recursivas em listas, como sum, são mais simples de definir usando foldr; ● Propriedades de funções definidas usando foldr podem ser provadas usando propriedades algébricas de foldr, como fusão e a regra da banana split; ● Otimizações avançadas de programa podem ser simples se foldr é usado ao invés de recursão explícita.
    143. 143. (.) :: (b -> c) -> (a -> b) -> (a -> c) f . g = x -> f (g x) 143 Outras funções de biblioteca ● A função de biblioteca (.) retorna a composição de duas funções como um única função. ● Por exemplo: odd :: Int -> Bool odd = not . even
    144. 144. 144 Outras funções de biblioteca (2) ● A função de biblioteca all decide se todos os elementos de uma lista satisfazem um dado predicado. all :: (a -> Bool) -> [a] -> Bool all p xs = and [p x | x <- xs] ● Por exemplo: > all even [2,4,6,8,10] True
    145. 145. 145 Outras funções de biblioteca (3) ● Similarmente, a função de biblioteca any decide se pelo menos um elemento de uma lista satisfaz um dado predicado. any :: (a -> Bool) -> [a] -> Bool any p xs = or [p x | x <- xs] ● Por exemplo: > any isSpace "abc def" True
    146. 146. takeWhile :: (a -> Bool) -> [a] -> [a] takeWhile p [] = [] takeWhile p (x:xs) 146 Outras funções de biblioteca (4) ● A função de biblioteca takeWhile seleciona elementos de uma lista enquanto um predicado for verdadeiro. | p x = x : takeWhile p xs | otherwise = [] ● Por exemplo: > takeWhile isAlpha "abc def" "abc"
    147. 147. dropWhile :: (a -> Bool) -> [a] -> [a] dropWhile p [] = [] dropWhile p (x:xs) 147 Outras funções de biblioteca (5) ● A função de biblioteca takeWhile seleciona elementos de uma lista enquanto um predicado for verdadeiro. | p x = dropWhile p xs | otherwise = x:xs ● Por exemplo: > dropWhile isSpace " abc" "abc"
    148. 148. 148 Exercícios ● Expresse a compreensão [f x | x ← xs, p x] utilizando as funções map e filter. ● Redefina map f e filter p usando foldr.
    149. 149. 149 Analisadores Funcionais Monads
    150. 150. 150 O que é um analisador (parser)? ● Um analisador é um programa que analisa um trecho de texto para determinar sua estrutura sintática. 2*3+4 significa + * 4 2 3
    151. 151. 151 Para que são utilizados? ● Praticamente todos programas do cotidiano usam alguma forma análise para pré-processar suas entradas: Hugs Unix Browser analisa Programas Haskell Scripts de shell Documentos HTML
    152. 152. 152 O tipo Parser ● Em uma linguagem funcional como Haskell, analisadores podem ser vistos naturalmente como funções: type Parser = String -> Tree Um analisador é uma função que toma uma string e Um analisador é uma função que toma uma string e retorna alguma forma de árvore retorna alguma forma de árvore
    153. 153. 153 O tipo Parser (2) ● No entanto, um analisador pode não requerer toda sua string de entrada, sendo assim também é retornada a entrada não utilizada. type Parser = String -> (Tree,String) ● Uma string pode ser analisada de várias maneiras, incluindo nenhuma, então generaliza-se para uma lista de resultados: type Parser = String -> [(Tree,String)]
    154. 154. 154 O tipo Parser (3) ● Finalmente, um analisador nem sempre pode produzir uma árvore, então generaliza-se para uma valor de qualquer tipo: type Parser = String -> [(a,String)] Nota: Para simplificar, esta sendo considerado somente analisadores que ou falham e retornam um lista vazia de resultados, ou têm sucesso e retornam uma lista singleton (apenas um valor). Nota: Para simplificar, esta sendo considerado somente analisadores que ou falham e retornam um lista vazia de resultados, ou têm sucesso e retornam uma lista singleton (apenas um valor).
    155. 155. 155 Analisadores básicos ● O analisador item falha se a entrada é vazia, e consome o primeiro caractere caso contrário: item :: Parser Char item = inp -> case inp of [] -> [] (x:xs) -> [(x,xs)]
    156. 156. 156 Analisadores básicos (2) ● O analisador failure sempre falha: failure :: Parser a failure = inp -> [] ● O analisador return sempre tem sucesso, retornando o valor v sem consumir nenhuma entrada: return :: a -> Parser a return v = inp -> [(v,inp)]
    157. 157. 157 Analisadores básicos (2) ● O analisador p +++ q se comporta como o analisador p se tiver sucesso, e como o analisador q caso contrário: (+++) :: Parser a -> Parser a -> Parser a p +++ q = inp -> case p inp of [] -> parse q inp [(v,out)] -> [(v,out)] ● A função parse aplica um analisador a uma string: parse :: Parser a -> String -> [(a,String)] parse p inp = p inp
    158. 158. 158 Exemplos ● O comportamento dos cinco analisadores primitivos podem ser ilustrados com exemplos simples: % hugs parse.hs > parse item “” [] > parse item “abc” [('a',”bc”)]
    159. 159. 159 Exemplos (2) > parse failure "abc" [] > parse (return 1) "abc" [(1,"abc")] > parse (item +++ return 'd') "abc" [('a',"bc")] > parse (failure +++ return 'd') "abc" [('d',"abc")]
    160. 160. 160 Nota ● O arquivo de biblioteca Parsing está disponível na web na home page de Programming in Haskell. ● O tipo Parser é um monad, uma estrutura matemática que ser provou útil na modelagem de vários tipos de computações diferentes.
    161. 161. 161 Sequenciamento ● Uma sequência de analisadores pode ser combinada um um único analisador composto utilizando a palavra-chave do. ● Por exemplo: p :: Parser (Char, Char) p = do x <- item item y <- item return (x,y)
    162. 162. 162 Sequenciamento (2) ● Se qualquer analisador em uma sequência de analisadores falhar, então a sequência interia falhará. Por exemplo: > parse p "abcdef" [(('a','c'), "def")] > parse p "ab" [] ● A notação do não é especificada no tipo Parser, mas pode ser utilizada com qualquer tipo monadico.
    163. 163. 163 Primitivas derivadas ● Analisando um caractere que satisfaz um predicado: sat :: (Char -> Bool) -> Parser Char sat p = do x <- item if p x then return x else failure
    164. 164. 164 Primitivas derivadas (2) ● Analisando um dígito e caracteres específicos: digit :: Parser Char digit = sat isDigit char :: Char -> Parser Char char x = sat (x ==) ● Aplicando um analisador zero ou mais vezes: many :: Parser a -> Parser [a] many p = many1 p +++ return []
    165. 165. 165 Primitivas derivadas (3) ● Aplicando um analisador uma ou mais vezes: many1 :: Parser a -> Parser [] many1 p = do v <- p vs <- many p return (v:vs) ● Analisando um string de caracteres específica: string :: String -> Parser String string [] = return [] string (x:xs) = do char x string xs return (x:xs)
    166. 166. 166 Exemplo ● Agora é possível definir um analisador que consome um lista de um ou mais dígitos de uma string: p :: Parser String p = do char '[' d <- digit ds <- many (do char ',' digit) char ']' return (d:ds)
    167. 167. 167 Exemplo (2) ● Agora é possível definir um analisador que consome um lista de um ou mais dígitos de uma string: > parse p "[1,2,3,4]" [("1234","")] > parse p "[1,2,3,4" [] Nota: Bibliotecas de análise mais sofisticadas podem indicar e/ou recuperar-se de erros na string de entrada. Nota: Bibliotecas de análise mais sofisticadas podem indicar e/ou recuperar-se de erros na string de entrada.
    168. 168. 168 Expressões aritméticas ● Considerando uma simples forma de expressões construídas a partir de dígitos únicos usando operações de adição + e multiplicação *, juntas com parênteses. ● Assumindo também que: – * e + associados a direita; – * tem prioridade maior do que +.
    169. 169. 169 Expressões aritméticas (2) ● Formalmente, a sintaxe de tais expressões é definida pela seguinte gramática livre de contexto: exp → term '+' expr | term term → factor '*' term | factor factor → digit | '(' expr ')' digit → '0' | '1' | … | '9'
    170. 170. 170 Expressões aritméticas (2) ● No entanto, por razões de eficiência, é importante fatorizar as regras para expr e term. exp → term ('+' expr | ε) term → term ('*' term | ε) Nota: O símbolo ε denota uma string vazia. Nota: O símbolo ε denota uma string vazia.
    171. 171. 171 Expressões aritméticas (2) ● Agora é fácil traduzir a gramática em um analisador que avalia expressões, simplesmente reescrevendo as regras da gramática utilizando as primitivas de análise. expr :: Parser Int expr = do t <- term do char '+' e <- expr return (t + e) +++ return t
    172. 172. 172 Expressões aritméticas (3) term :: Parser Int term = do f <- factor do char '*' t <- term return (f * t) +++ return f factor :: Parser Int factor = do d <- digit return (digitToInt d) +++ do char '(' e <- expr char ')' return e
    173. 173. 173 Expressões aritméticas (4) ● Finalmente, definido: eval :: String -> Int eval xs = fst (head (parse expr xs)) ● E testando alguns exemplos: > eval "2*3+4" 10 > eval "2*(3+4)" 14
    174. 174. 174 Programas Interativos
    175. 175. 175 Introdução ● Até agora vimos como Haskell pode ser usado para escrever programas em lote que tomam todas as suas entradas no início e retornam todas as suas saídas no final. programa em lote programa em lote entradas saídas
    176. 176. 176 teclado programa interativo entradas saídas tela Introdução (2) ● No entanto, também gostaríamos de usar Haskell para escrever programas interativos que leem a partir de um teclado e escrevem na tela, enquanto estiverem em execução.
    177. 177. 177 O problema ● Programas em Haskell são funções matemáticas puras: – Programas em Haskell não têm efeitos colaterais. ● No entanto, ler de um teclado e escrever em uma tela são efeitos colaterais: – Programas interativos tem efeitos colaterais.
    178. 178. 178 A solução ● Programas interativos podem ser escritos em Haskell usando tipos para distinguir expressões puras de ações impuras que podem envolver efeitos colaterais. IIOO aa Os tipos de ações que retornam um valor do tipo a. Os tipos de ações que retornam um valor do tipo a.
    179. 179. 179 Por exemplo IIOO CChhaarr IIOO (()) O tipo de ações que retornam um caractere. O tipo de ações que retornam um caractere. O tipo de ações puramente com efeitos colaterais que não retornam nenhum O tipo de ações puramente com efeitos colaterais que não retornam nenhum valor. valor. Nota: () é um tipo de tupla sem componentes. Nota: () é um tipo de tupla sem componentes.
    180. 180. – A ação getChar lê um caractere do teclado, ecoa na tela, e retorna o caractere como seu valor de resultado. 180 Ações básicas ● A biblioteca padrão fornece várias ações incluindo as seguintes ações primitivas: getChar :: IO Char
    181. 181. 181 Ações básicas (2) ● A ação putChar c escreve o caractere c na tela, e não retorna nenhum valor como resultado: putChar :: Char -> IO () ● A ação return v simplesmente retorna o valor v, sem realizar nenhuma interação: return :: a -> IO a
    182. 182. 182 Sequenciamento ● Uma sequência de ações podem ser combinadas como uma única ação composta usando a palavra do. a :: IO (Char,Char) a = do x <- getChar getChar Y <- getChar return (x,y) ● Por exemplo:
    183. 183. 183 getLine :: IO String getLine = do x <- getChar if x == 'n' then return [] else do xs <- getLine return (x:xs) Primitivas derivadas ● Lendo uma string a partir do teclado:
    184. 184. 184 putStr :: String -> IO () putStr [] = return () putStr (x:xs) = do putChar x putStr xs putStrLn :: String -> IO () putStrLn xs = do putStr xs putChar 'n' Primitivas derivadas(2) ● Escrevendo uma string na tela: ● Escrevendo uma string e movendo para uma nova linha:
    185. 185. 185 strlen :: IO () strlen = do putStr "Enter a string: " Xs <- getLine putStr "The string has " putStr (show (length xs)) putStrLn " characters" Exemplo ● Agora é possível definir uma ação que espera uma string ser entrada e mostra seu comprimento:
    186. 186. 186 Exemplo (2) > strlen Enter a string: abcde The string has 5 characters Nota: Avaliar uma ação executa seus efeitos colaterais, com seu valor final de resultado sendo descartado. Nota: Avaliar uma ação executa seus efeitos colaterais, com seu valor final de resultado sendo descartado.
    187. 187. 187 Jogo da forca ● Considere a seguinte versão do jogo da forca: – Um jogador digita uma palavra secretamente; – O outro jogador tenta deduzir a palavra, entrando uma sequência de “chutes”; – Para cada “chute”, o computador indica quais letras da palavra secreta ocorrem no “chute”; – O jogo acaba quando o “chute” estiver correto.
    188. 188. 188 Jogo da forca (2) ● Foi adotado a abordagem top-down para implementar o jogo da forca em Haskell, iniciando como se segue: hangman :: IO () hangman = do putStrLn "Think of a word: " Word <- sgetLine putStrLn "Try to guess it:" guess word
    189. 189. 189 sgetLine :: IO String sgetLine = do x <- getCh if x == 'n' then do putChar x return [] else do putChar '-' Xs <- sgetLine return (x:xs) Jogo da forca (3) ● A ação sgetLine lê um linha de texto do teclado, ecoando cada caractere como um traço -:
    190. 190. 190 import System.IO getCh :: IO Char getCh = do hSetEcho stdin False C <- getChar hSetEcho stdin True return c Jogo da forca (4) ● A ação getCh lê um único caractere do teclado, sem ecoar para a tela:
    191. 191. 191 guess :: String -> IO () guess word = do putStr "> " Xs <- getLine if xs == word then putStrLn "You got it!" else do putStrLn (diff word xs) guess word Jogo da forca (5) ● A função guess é o loop principal, que requisita e processa os “chutes” até o jogo terminar:
    192. 192. Jogo da forca (6) ● A função diff indica quais caracteres de uma string ocorrem na segunda string: [if elem x ys then x else '-' | x <- xs] 192 diff :: String -> String -> String diff xs ys = ● Por exemplo: > diff "haskell" "pascal" "-as--ll"
    193. 193. Dica: Represente o tabuleiro como uma lista de cinco inteiros que representam o número de asteriscos remanescentes em cada linha. Por exemplo, o tabuleiro inicial é: [5,4,3,2,1]. 193 1: * * * * * 2: * * * * 3: * * * 4: * * 5: * Exercício ● Implemente o jogo nim em Haskell – Regras: ● O tabuleiro é composto de 5 linhas de asteriscos; ● Dois jogadores revesam a remoção de um ou mais asteriscos do fim de uma única linha; ● O ganhador é o jogador que remover o último asterisco ou asteriscos do tabuleiro. Dica: Represente o tabuleiro como uma lista de cinco inteiros que representam o número de asteriscos remanescentes em cada linha. Por exemplo, o tabuleiro inicial é: [5,4,3,2,1].
    194. 194. Capítulo 10 – Declarando tipos e classes
    195. 195. Declarações de tipo ● Em Haskell, um novo nome para um tipo existente pode ser definido usando uma declaração de tipo. type String = [Char] String String éé uumm ssiinnôônniimmoo ddee ttiippoo [[CChhaarr]]..
    196. 196. Declarações de tipo (2) ● Declarações de tipo podem ser usadas para tornar outros tipos mais simples de ler. Por exemplo, dado type Pos = (Int,Int) ● Pode-se definir: origin :: Pos origin = (0,0) left :: Pos -> Pos left (x,y) = (x-1,y)
    197. 197. Declarações de tipo (3) ● Como definições de funções, declarações de tipo também podem conter parâmetros. Por exemplo, dado type Pair a = (a,a) ● Pode-se definir: mult :: Pair Int -> Int mult (m,n) = m*n copy :: a -> Pair a copy x = (x,x)
    198. 198. Declarações de tipo (3) ● Declarações de tipo podem ser aninhadas: type Pos = (Int,Int) type Trans = Pos -> Pos ● No entando, não podem ser recursivas: type Tree = (Int,[Tree])
    199. 199. 199 Declarações de dados ● Um tipo completamente novo pode ser definido especificando seus valores uma uma declaração de dados. data Bool = False | True Bool é um novo tipo, com dois novos valores False e True. Bool é um novo tipo, com dois novos valores False e True.
    200. 200. Declarações de dados (2) ● Nota: – Os dois valores False e True podem ser chamados de construtores do tipo Bool; – Nomes de tipos e construtores devem iniciar com letra maiúscula; – Declarações de dados são similares à gramáticas livres de contexto. O primeiro especifica os valores do tipo, e o último as sentenças da linguagem.
    201. 201. Declarações de dados (3) ● Valores de novos tipos podem ser usados da mesma forma dos tipos nativos. Por exemplo, dado data Answer = Yes | No | Unknown ● Pode-se definir: answers :: [Answer] answers = [Yes,No,Unknown] flip :: Answer -> Answer flip Yes = No flip No = Yes flip Unknown = Unknown
    202. 202. Declarações de dados (4) ● Os construtores um uma declaração de dados também podem possuir parâmetros. Por exemplo, dado data Shape = Circle Float ● Pode-se definir: | Rect Float Float square :: Float -> Shape square n = Rect n n area :: Shape -> Float area (Circle r) = pi * r^2 area (Rect x y) = x * y
    203. 203. Declarações de dados (5) ● Nota: – Shape possui valores da forma Circle r onde r é float, e Rect x y onde x e y são floats. – Circle e Rect podem ser vistos como funções que constroem valores do tipo Shape: Circle :: Float -> Shape Rect :: Float -> Float -> Shape
    204. 204. Declarações de dados (6) ● Sem surpresas, as próprias declarações de dados podem conter parâmetros. Por exemplo, dado data Maybe a = Nothing | Just a ● Pode-se definir: safediv :: Int -> Int -> Maybe Int safediv _ 0 = Nothing safediv m n = Just (m `div` n) safehead :: [a] -> Maybe a safehead [] = Nothing safehead xs = Just (head xs)
    205. 205. 205 Referências ● Meijer, Erik. C9 Lectures: Functional Programming Fundamentals. Disponível em: <http://channel9.msdn.com/Series/C9-Lectures-Erik- Meijer-Functional-Programming-Fundamentals/Lecture- Series-Erik-Meijer-Functional-Programming- Fundamentals-Chapter-1>

    ×