Programando com Funções: Introdução Recursão Notação Programação Funcional Carlos Camarão Universidade Federal de Minas Gerais DCC-UFMG 2008 www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação Padrões Exemplo Usados para definir funções por meio de equações: permite determinar construtor usado no argumento e seus componentes. Ex: length (a:x) = 1 + length x length [] = 0 Considere: length [1,2] Notação [1,2] é abreviação (“açúcar sintático”) para 1:2:[] Chamada envolve casamento de 1:2:[] com a:x — sucesso, fazendo a denotar 1 e x denotar 2:[] Chamada recursiva length (2:[]) envolve casamento com a:x (com sucesso), fazendo agora a denotar 2 e x denotar [] Última chamada length [] faz com que casamento com padrão a:x falhe: construtor (:) é diferente de []. Casamento com padrão da próxima equação feito com sucesso www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação . . . Padrão Formado por variáveis e construtores (constantes). Expressão que não envolve chamada de função Exemplos: 3 True x [a,b] a:x PesoArg 10 Construtores (constantes) não funcionais Variável Construtores (constantes) ... funcionais Variável casa com qualquer expressão; construtor (constante) só casa com si próprio; necessário casamento com componentes, se existirem www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação Definindo funções Exemplos fac n odd x square x sum_square l = = = = product [1..n] not (even x) x * x sum (map square l) Usando composição de funções, podemos também definir: odd sum_square = not . even = sum . map square www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação . . . Definições de funções Exemplos Funções com mais de um parâmetro (funções que retornam funções): combinations n k = fac n ‘div ‘ (fac k * fac (n-k )) raizes a b c = [(-b + sqrt (b*b - 4.0*a*c)) / (2.0*a) ,(-b - sqrt (b*b - 4.0*a*c)) / (2.0*a) ] www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação Definição de Função: sintaxe 1 Nome 2 Padrões — um para cada parâmetro 3 Símbolo = 4 Expressão (corpo) que define o resultado Erro comum: confundir operador de comparação de igualdade == com =: negative x = x < 0 positive x = x > 0 isnull x = x == 0 www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação Definições usando Padrões Operador &&, definido no Prelude, pode ser definido do seguinte modo (usando padrões): False False True True && && && && False True False True = = = = False False False True Definição seguinte semanticamente equivalente mas mais concisa: True && True = True _ && _ = False Definição seguinte similar — mas não semanticamente equivalente, e mais eficiente: True && b = b False && _ = False www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação Casamento de Padrões Exemplos Realizado segundo ordem das equações na definição da função. Por exemplo, toda chamada à seguinte função retorna False: _ && _ = False True && True = True Padrão não pode repetir nenhuma variável. Por exemplo, a seguinte definição é inválida: b && b = b _ && _ = False www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação Funções “currificadas” Funções que retornam funções podem ser vistas informalmente como funções com mais de um argumento. Exemplo: add :: Int → (Int → Int) add x y = x+y Função add recebe um inteiro x e retorna a função add x; por sua vez, esta função recebe um inteiro y e retorna x+y . www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação Porque funções currificadas são úteis? Podemos definir novas funções por meio de aplicação parcial: add :: Int → (Int → Int) add x y = x+y inc :: Int → Int inc = add 1 www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação Listas, repetição e recursão Listas são muito usadas em programação funcional. Em vez de usar comandos de repetição (for, while etc.) e variáveis cujos valores podem ser modificados por comandos de atribuição, o modo mais básico de repetição em Haskell envolve “percorrer” valores contidos em uma estrutura de dados definida recursivamente, comumente uma lista, usando uma função recursiva. O modo não básico envolve uso de funções de ordem superior, que encapsulam internamente o uso da recursão www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação Função de Ordem Superior Exemplos Função que recebe ou retorna uma função map:: (a → b) → [a] → [b] map f [] = [] map f (a:x) = f a : map f x > [ > [ > [ map fac [ 1,2,3,4,5 ] 1, 2, 6, 24, 120 ] map sqrt [ 1.0,2.0,3.0,4.0 ] 1.0, 1.41421, 1.73205, 2.0 ] map even [ 1..6 ] False, True, False, True, False, True ] (.) :: (b → c) → (a → b) → a → c (f . g) x = f (g x) www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação Definições locais Definições locais podem ocorrer, usando where ou let: raizes a b c = [ (-b+d) / n, (-b-d) / n ] where d = sqrt (b*b - 4.0*a*c) n = 2.0*a raizes a b c = let d = sqrt (b*b - 4.0*a*c) n = 2.0*a in [ (-b+d) / n, (-b-d) / n ] www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação Definições com guardas Definição de função pode utilizar equações com guardas Exemplo: signum x | x > 0 = 1 | x == 0 = 0 | x < 0 = -1 Definição equivalente a: signum x = if x>0 then 1 else if x==0 then 0 else -1 www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação Aplicação de Funções associatividade à esquerda operação de maior precedência Exemplo: f x y z - 1 + g 4 equivalente a: (((f x) y ) z) - 1 + (g 4) www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação Estratégia de avaliação preguiçosa Haskell usa uma estratégia preguiçosa (em inglês, lazy ) de avaliação de expressões: expressão é avaliada se e somente se o seu resultado é necessário na execução do programa Resultado é necessário tipicamente para casamento de padrão Exemplo: repeat x = x : repeat x one = head (repeat 1) Se a computação de one é necessária — para casamento de padrão como, por exemplo, em print one — a avaliação da expressão head (repeat 1) Programação Funcional éwww.dcc.ufmg.br/~camarao/cursos/fp feita apenas para fornecer o resultado 1: Programando com Funções: Introdução Recursão Notação Vantagens da avaliação preguiçosa em relação à gulosa: primeiras observações Permite definir na própria linguagem funções úteis e comumente usadas — como &&, || e if-then-else Permite definição e uso de valores “ilimitados” — como por exemplo repeat 1 e [2..]) mas: análise de complexidade de tempo e espaço é mais complexa www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação Definições Recursivas Funções podem ser definidas recursivamente — indutivamente, se existir caso não recursivo e caso recursivo sobre argumento “menor” Exemplo: fat 0 = 1 fat n = n * fat (n-1) www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação Exercício Haskell provê uma função lines:: String → [String] para separação de um texto em linhas, omitindo os caracteres de separação de linhas (’\n’). Por exemplo: > lines "lin1\nlin2" ["lin1","lin2"] > lines "a*b\n\nxx!" ["a*b","","xx!"] lines considera \n como separador de linhas. No Windows, a separação de linhas, em arquivos que armazenam textos (seqüências de caracteres separadas em linhas), é indicada por dois caracteres: \r\n. Ao usarmos um arquivo-texto Windows em um programa Unix, o caractere \r será gravado no final de cada linha. www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação Regra de Leiaute Haskell usa uma “regra de leiaute” para reduzir uso de símbolos delimitadores e permitir o uso de espaços para expressar aninhamento de definições em programas. raizes a b c = [ (-b+d) / n, (-b-d) / n ] where d = sqrt (b*b - 4.0*a*c) n = 2.0*a A posição (coluna) c de início da primeira definição em um bloco b determina que: outras definições em b bloco devem começar na mesma posição c; início de definição em posição maior (menor) que c indica aninhamento mais interno (externo) em relação a b. www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação . . . Regra de leiaute Opcional Estrutura de blocos pode ser expressa explicitamente, sem uso da regra de leiaute, por meio de delimitadores: abre-chaves para iniciar e fecha-chaves para finalizar blocos, e ; para separar definições em um bloco. f x y = g (x + w) where g u = u + v where v = u * u w = 2 + y Expressa a mesma função (embora de modo mais legível) que: f x y = g (x + w) where { g u = u + v where { v = u * u}; w = 2 + y } www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional Programando com Funções: Introdução Recursão Notação Comentários Comentários podem ser introduzidos de duas formas: dois hífens - iniciam um comentário que vai até o fim da linha os caracteres {- iniciam e os caracteres -} terminam um comentário www.dcc.ufmg.br/~camarao/cursos/fp Programação Funcional