Professional Documents
Culture Documents
Programao Funcional
Lic. Matemtica e Cincias da Computao
2005 / 2006
Um programa para converter valores de temperaturas em graus Celcius para graus Farenheit, e de graus Kelvin para graus Celcius. celFar c = c * 1.8 + 32 kelCel k = k - 273
( mjf@di.uminho.pt )
Depois de carregar este programa no interpretador Haskell, podemos fazer os seguintes testes:
Programa Resumido
Nesta disciplina estuda-se o paradigma funcional de programao, tendo por base a linguagem de programao Haskell. Programao funcional em Haskell. Conceitos fundamentais: expresses, tipos, reduo, funes e recursividade. Conceitos avanados: funes de ordem superior, polimorfismo, tipos indutivos, classes, modularidade e monades.
A um conjunto de associaes nome-valor d-se o nome de ambiente ou contexto (ou programa). As expresses so calculadas no mbito de um contexto e podem conter ocorrncias dos nomes definidos nesse contexto. O interpretador usa as definies que tem no contexto (programa) como regras de clculo, para simplificar (calcular) o valor de uma expresso. Exemplo:
kelFar 300
celFar (kelCel 300) (kelCel 300) * 1.8 + 32 (300 273) * 1.8 + 32 27 * 1.8 + 32 80.6
Transparncia Referencial
No paradigma funcional, as expresses: so a representao concreta da informao; podem ser associadas a nomes (definies); denotam valores que so determinados pelo interpretador da linguagem. No mbito de um dado contexto, todos os nomes que ocorrem numa expresso tm um valor nico e imotvel. O valor de uma expresso depende unicamente dos valores das sub-expresses que a constituem, e essas podem ser substituidas por outras que possuam o mesmo valor. A esta caracteristica d-se o nome de transparncia referencial.
1980s Miranda (strongly typed, type inference, polymorphism, lazy evaluation) 1990s Haskell (strongly typed, type inference, polymorphism, lazy evaluation, ad-hoc polymorphism, monadic IO)
Linguagens Funcionais
O nome de linguagens funcionais advm do facto de estas terem como operaes bsicas a definio de funes e a aplicao de funes.
Haskell
O Haskell uma linguagem puramente funcional, fortemente tipada, e com um sistema de tipos extremamente evoluido. A linguagem usada neste curso o Haskell 98. Exemplos de interpretadores e um compilador para a linguagem Haskell 98:
Nas linguagens funcionais as funes so entidades de 1 classe, isto , podem ser usadas como qualquer outro objecto: passadas como parmetro, devolvidas como resultado, ou mesmo armazenadas em estruturas de dados. Isto d s linguagens funcionais uma grande flexibilidade, capacidade de abstrao e modularizao do processamento de dados. As linguagens funcionais fornecem um alto nivel de abstrao, o que faz com que os programas funcionais sejam mais concisos, mais fceis de entender / manter e mais rpidos de desenvolver do que programas imperativos. No entanto, em certas situaes, os programas funcionias podem ser mais penalizadores em termos de eficincia.
Hugs Haskell User's Gofer System GHC Glasgow Haskell Compiler ( o que vamos usar ...)
www.haskell.org
6 8
Tipos
Haskell
Haskell is a general purpose, purely functional programming language incorporating many recent innovations in programming language design. Haskell provides higher-order functions, non-strict semantics, static polymorphic typing, user-defined algebraic datatypes, patternmatching, list comprehensions, a module system, a monadic I/O system, and a rich set of primitive datatypes, including lists, arrays, arbitrary and fixed precision integers, and floatingpoint numbers. Haskell is both the culmination and solidification of many years of research on lazy functional languages. (The Haskell 98 Report)
Os tipos servem para classificar entidades (de acordo con as suas caracteristicas). Em Haskell toda a expresso tem um tipo.
e :: T
Exemplos: 58 'a' [3,5,7] (8,'b') :: :: :: ::
e tem tipo T
do tipo
Em Haskell, a verificao de tipos feita durante a compilao. O Haskell uma linguagem fortemente tipada, com um sistema de tipos muito evoludo (como veremos).
9 11
Tipos Bsicos
Bool Char Int Boleanos: Caracteres: True, False 'a', 'b', 'A', '1', '\n', '2', ... 1, -3, 234345, ... 2, -7, 75756850013434682, ... 3.5, -6.53422, 51.2E7, 3e4, ...
> 2 < 35 True > not True False > not ((3.5+6.7) > 23) True
Nm. vrg. flut. de dupla preciso: 3.5, -6.5342, 51.2E7, ... Unit () o seu nico elemento do tipo Unit.
10
12
Tipos Compostos
Produtos Cartesianos (T1,T2, ...,Tn)
(T1,T2,...,Tn) o tipo dos tuplos com o 1 elemento do tipo T1, 2 elemento do tipo T2, etc. Exemplos:
(1,5) :: (Int,Int) ('a',6,True) :: (Char,Int,Bool)
Definies
Uma definio associa um nome a uma expresso. nome = expresso nome tem que ser uma palavra comeada por letra minscula. A definio de funes pode ainda ser feita por um conjunto de equaes da forma: nome arg1 arg2 ... argn = expresso Quando se define uma funo podemos incluir informao sobre o seu tipo. No entanto, essa informao no obrigatria.
Exemplos: pi = 3.1415 areaCirc x = pi * x * x areaQuad = \x -> x*x areaTri b a = (b*a)/2 volCubo :: Float -> Float volCubo y = y * y * y
Listas [T]
[T] o tipo da listas cujos elementos so todos do tipo T. Exemplos: [2,5,6,8] :: [Integer] ['h','a','s'] :: [Char] [3.5,86.343,1.2] :: [Float]
Funes T1 -> T2
T1 -> T2 o tipo das funes que recebem valores do tipo T1 e devolvem valores do tipo T2. Exemplos: not :: Bool -> Bool ord :: Char -> Int
13
15
Funes
A operao mais importante das funes a sua aplicao. Se f :: T1 -> T2 e a :: T1 ento f a :: T2
Plimorfismo
O tipo de cada funo inferido automticamente pelo interpretador.
Exemplo:
Exemplos:
> not True False :: Bool > ord 'a' 97 ::Int > ord 'A' 65 :: Int > chr 97 'a' :: Char
O tipo inferido
Porqu ?
id x = x nl y = '\n'
Novas definies de funes devero que ser escritas num ficheiro, que depois ser carregado no interpretador.
14
16
O problema resolvido recorrendo a variveis de tipo. Uma varivel de tipo representa um tipo qualquer. id :: a -> a nl :: a -> Char Em Haskell: As variveis de tipo representam-se por nomes comeados por letras minsculas
(normalmente a, b, c, ...).
O Haskell tem um enorme conjunto de definies (que est no mdulo Prelude) que carregado por defeito e que constitui a base da linguagem Haskell. Alguns operadores: Lgicos: && (e), || (ou), not (negao)
Numricos:
+, -, *, / (diviso de reais), ^ (exponenciao com inteiros), div (diviso inteira), mod (resto da diviso inteira), ** (exponenciaes com reais), log, sin, cos, tan, ...
Os tipos concretos usam nomes comeados por letras maisculas (ex: Bool, Int, ...). Quando as funes so usadas, as variveis de tipos so substitudas pelos tipos concretos adquados.
Exemplos:
Relacionais: == (igualdade), /= (desigualdade), <, <=, >, >= Condicional: if ... then ... else ... :: Bool Exemplo: :: a
> if (3>=5) then [1,2,3] else [3,4] [3,4] > if (ord 'A' == 65) then 2 else 3 2
19
id id nl nl
id id nl nl
:: :: :: ::
Bool -> Bool Char ->Char Bool -> Char Float -> Char
17
Funes cujos tipos tm variveis de tipo so chamadas funes polimrficas. Um tipo pode conter diferentes variveis de tipo.
Exemplo: fst (x,y) = x fst :: (a,b) -> a
Mdulos
Um programa Haskell est organizado em mdulos. Cada mdulo uma coleco de funes e tipos de dados, definidos num ambiente fechado. Um mdulo pode exportar todas ou s algumas das suas definies. (...)
Inferncia de tipos O tipo de cada funo inferido automticamente. O Haskell infere o tipo mais preciso de qualquer expresso.
Ao arrancar o interpretador do GHC, ghci, este carrega o mdulo Prelude (que contm um enorme conjunto de declaraes) e fica espera dos pedidos do utilizador.
possivel associar a uma funo um tipo mais especifico do que o tipo inferido automticamente.
Exemplo: seg :: (Bool,Int) -> Int seg (x,y) = y
ghci
___ ___ _ / _ \ /\ /\/ __(_) / /_\// /_/ / / | | / /_\\/ __ / /___| | \____/\/ /_/\____/|_| GHC Interactive, version 6.2.1, for Haskell 98. http://www.haskell.org/ghc/ Type :? for help.
O utilizador pode fazer dois tipos de pedidos ao interpretador ghci: Calcular o valor de uma expresso. Prelude> 3+5 8 Prelude> (5>=7) || (3^2 == 9) True Prelude> fst (40/2,'A') 20.0 Prelude> pi 3.141592653589793 Prelude> aaa <interactive>:1: Variable not in scope: `aaa' Prelude> Executar um comando. Os comandos do ghci comeam sempre por dois pontos ( : ). O comando :? lista todos os comandos existentes Prelude> :? Commands available from the prompt: ...
21 23
Depois de carregar um mdulo, os nomes definidos nesse mdulo passam a estar disponveis no ambiente de interpretao
Prelude> kelCel 300 <interactive>:1: Variable not in scope: `kelCel' Prelude> :load Temp Compiling Temp ( Temp.hs, interpreted ) Ok, modules loaded: Temp. *Temp> kelCel 300 27 *Temp>
Inicialmente, apenas as declaraes do mdulo Prelude esto no ambiente de interpretao. Aps o carregamento do ficheiro Temp.hs, ficam no ambiente todas a definies feitas no mdulo Temp e as definies do Prelude.
Alguns comandos teis: :quit ou :q :type ou :t termina a execuo do ghci. indica o tipo de uma expresso. Prelude> :type (2>5) (2>5) :: Bool Prelude> :t not not :: Bool -> Bool Prelude> :q Leaving GHCi.
Um mdulo constitui um componente de software e d a possibilidade de gerar bibliotecas de funes que podem ser reutilizadas em diversos programas Haskell. Exemplo: Muitas funes sobre caracteres esto definidas no mdulo Char do GHC. Para se utilizarem declaraes feitas noutros mdulos, que no o Prelude, necessrio primeiro fazer a sua importao atravs da instruo:
import Nome_do_mdulo
:load ou :l Exemplo: carrega o programa (o mdulo) que est num dado ficheiro.
Exemplo.hs
module Exemplo where import Char letra :: Int -> Char letra n = if (n>=65 && n<=90) || (n>=97 && n<=122) then chr n else ' ' numero :: Int -> Char numero n = if (n>=48 && n<=57) then chr n else ' '
22 24
Considere o seguinte programa guardado no ficheiro Temp.hs Temp.hs module Temp where celFar c = c * 1.8 + 32 kelCel k = k - 273 kelFar k = celFar (kelCel k)
Comentrios
possvel colocar comentrios num programa Haskell de duas formas:
-{- ... -}
O texto que aparecer a seguir a -- at ao final da linha ignorado pelo interpretador. O texto que estiver entre {- e -} no avaliado pelo interpretador. Podem ser vrias linhas.
uma forma abreviada de escrever f :: T1 -> (T2 -> (... -> (Tn -> T)...))
module Temp where -- de Celcius para Farenheit celFar c = c * 1.8 + 32 -- de Kelvin para Celcius kelCel k = k - 273 -- de Kelvin para Farenheit kelFar k = celFar (kelCel k) {- dado valor da temperatura em Kelvin, retorna o triplo com o valor da temperatura em Kelvin, Celcius e Farenheit -} kelCelFar k = (k, kelCel k, kelFar k)
25
27
test (x,y) = [ (not x), (y || x), (x && y) ] test' x y = [ (not x), (y || x), (x && y) ]
Tm tipos diferentes !
A funo test recebe um nico argumento (que um par de booleanos) e devolve uma lista de booleanos. test :: (Bool,Bool) -> [Bool] > test (True,False) A funo test' recebe dois argumentos, cada um do tipo Bool, e devolve uma lista de booleanos. test' :: Bool -> Bool -> [Bool] > test' True False A funo test' recebe um valor de cada vez. Realmente, o seu tipo : test' :: Bool -> (Bool -> [Bool]) > (test' True) False Mas os parentesis podem ser dispensados ! 26
28
Qual ser o tipo de cada uma destas funes ? D exemplos da sua invocao.
Lista e String
[a]
o tipo das listas cujos elementos so todos do tipo a . [2,5,6,8] [(1+3,'c'),(8,'A'),(4,'d')] [3.5, 86.343, 1.2*5] ['O','l','a'] :: :: :: :: [Integer] [(Int,Char)] [Float] [Char]
Exemplos:
unwords :: [String] -> String constri um texto a partir de uma lista de palavras. lines :: String -> [String] d a lista de linhas de um texto (i.e. parte pelo '\n' ).
Exemplos:
Prelude> words "aaaa bbbb cccc\tddddd eeee\nffff gggg hhhh" ["aaaa","bbbb","cccc","ddddd","eeee","ffff","gggg","hhhh"] Prelude> unwords ["aaaa","bbbb","cccc","ddddd","eeee","ffff","gggg","hhhh"] "aaaa bbbb cccc ddddd eeee ffff gggg hhhh" Prelude> lines "aaaa bbbb cccc\tddddd eeee\nffff ["aaaa bbbb cccc\tddddd eeee","ffff gggg hhhh"] Prelude> reverse "programacao funcional" "lanoicnuf oacamargorp" 29 31 gggg hhhh"
String
Exemplo:
Os valores do tipo String tambm se escrevem de forma abreviada entre haskell > Ola True
calcula o primeiro elemento da lista. calcula a lista sem o primeiro elemento. d um segmento inicial de uma lista. d um segmento final de uma lista.
take :: Int -> [a] -> [a] drop :: Int -> [a] -> [a] reverse :: [a] -> [a] last :: [a] -> a Exemplos:
Prelude> head [3,4,5,6,7,8,9]
{ 2x | x {n|n
{10,3,7,2} } {9,8,-2,-10,3} 0
n+2
10 }
= [8,-2,3]
3
Prelude> tail ['a','b','c','d']
{ (x,y) | x {3,4,5}
y {9,10} }
Listas infinitas
{5,10, ... } { x | x
[5,10..] = [5,10,15,20,25,30,35,40,45,50,55,...
Padres (patterns)
Um padro uma varivel, uma constante, ou um esquema de um valor atmico (isto , o resultado de aplicar construtores bsicos dos valores a outros padres). No Haskell, um padro no pode ter variveis repetidas (padres lineares). Exemplos: = [0,8,46,216,...
par(x) }
[ x^3 | x <- [0..], even x ]
Mais exemplos:
Prelude> ['A'..'Z']
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" Prelude> ['A','C'..'X'] "ACEGIKMOQSUW" Prelude> [50,45..(-20)] [50,45,40,35,30,25,20,15,10,5,0,-5,-10,-15,-20] Prelude> drop 20 ['a'..'z'] "uvwxyz" Prelude> take 10 [3,3..] [3,3,3,3,3,3,3,3,3,3]
33
Quando no nos interessa dar nome a uma varivel, podemos usar _ que representa uma varivel annima nova. Exemplos: snd (_,x) = x segundo (_,y,_) = y
35
Equaes e Funes
Uma funo pode ser definida por equaes que relacionam os seus argumentos com o resultado pretendido. Exemplos:
Exemplos:
soma :: (Int,Int) -> Int -> (Int,Int) soma (x,y) z = (x+z, y+z)
triplo x = 3 * x dobro y = y + y perimCirc r = 2*pi*r perimTri x y z = x+y+z minimo x y = if x>y them y else x
As equaes definem regras de clculo para as funes que esto a ser definidas. nome arg1 arg2 ... argn = expresso
Nome da funo (iniciada por letra minscula). Argumentos da funo. Cada argumento um padro.
(cada varivel no pode ocorrer mais do que uma vez)
O tipo da funo inferido tendo por base que ambos os lados da equao tm que ter o mesmo tipo.
34 36
Reduo
O clculo do valor de uma expresso feito usando as equaes que definem as funes como regras de clculo. Uma reduo um passo do processo de clculo ( usual usar o smbolo
denotar esse paso)
(triplo (snd (9,8)))+(triplo (snd (9,8))) (3*(snd (9,8))) + (triplo (snd (9,8))) (3*(snd (9,8))) + (3*(snd (9,8))) (3*8) + (3*(snd (9,8))) 24 + (3*(snd (9,8))) 24 + (3*8) 24 + 24 48
Cada reduo resulta de substituir a instncia do lado esquerdo da equao (o redex) pelo respectivo lado direito (o contractum). Exemplos: Relembre as seguintes funes triplo x = 3 * x dobro y = y + y snd (_,x) = x nl x = '\n' 21
Com a estrategia lazy os parametros das funes s so calculados se o seu valor fr mesmo necessrio. nl (triplo (dobro (7*45)) '\n'
Exemplos:
triplo 7
3*7
A lazy evaluation faz do Haskell uma linguagem no estrita. Esto , uma funo aplicada a um valor indefinido pode ter em Haskell um valor bem definido. nl (3/0) '\n'
A lazy evaluation tambm vai permitir ao Haskell lidar com estruturas de dados infinitas.
39
Podemos definir uma funo recorrendo a vrias equaes. A expresso dobro (triplo (snd (9,8))) pode reduzir de trs formas distintas:
dobro (triplo (snd (9,8))) dobro (triplo (snd (9,8))) dobro (triplo (snd (9,8))) dobro (triplo 8) dobro (3*(snd (9,8))) (triplo (snd (9,8)))+(triplo (snd (9,8)))
Exemplo:
h h h h
Todas as equaes tm que ser bem tipadas e de tipos coincidentes. Cada equao usada como regra de reduo. Quando uma funo aplicada a um argumento, a equao que selecionada como regra de reduo a 1 equao (a contar de cima) cujo padro que tem como argumento concorda com o argumento actual (pattern matching). Exemplos: h ('a',5) h ('b',4) h ('B',9) 3*5 4+4 9 15 8
A estratgia de reduo usada para o clculo das expresses uma caracterstica essencial de uma linguagem funcional. O Haskell usa a estratgia lazy evaluation (call-by-name), que se caracteriza por escolher para reduzir sempre o redex mais externo. Se houver vrios redexes ao mesmo nvel escolhe o redex mais esquerda (outermost; leftmost). Uma outra estratgia de reduo conhecida a eager evaluation (call-by-value), que se caracteriza por escolher para reduzir sempre o redex mais interno. Se houver vrios redexes ao mesmo nvel escolhe o redex mais esquerda (innermost; leftmost).
Note: Podem existir vrias equaes com padres que concordam com o argumento
actual. Por isso, a ordem das equaes importante, pois define uma prioridade na escolha da regra de reduo. O que acontece se alterar a ordem das equaes que definem h ?
38 40
Definies Locais
Uma definio associa um nome a uma expresso. Todas as definies feitas at aqui podem ser vistas como globais, uma vez que elas so visveis no mdulo do programa aonde esto. Mas, muitas vezes til reduzir o mbito de uma declarao. Em Haskell h duas formas de fazer definies locais: utilizando expresses let ... in ou atravs de clusulas where junto da definio equacional de funes.
Funo total
Porqu ?
> testa 5 320 > c Variable not in scope: `c' > f a Variable not in scope: `f' Variable not in scope: `a'
parc :: (Bool,Bool) -> Bool parc (True,False) = False parc (True,x) = True
242
Funo parcial
Porqu ?
Tipos Simnimos
O Haskell pode renomear tipos atravs de declaraes da forma:
Layout
Ao contrrio de quase todas as linguagens de programao, o Haskell no necessita de marcas para delimitar as diversas declaraes que constituem um programa. Em Haskell a identao do texto (isto , a forma como o texto de uma definio est disposto), tem um significado bem preciso. Regras fundamentais: 1. Se uma linha comea mais frente do que comeou a linha anterior, ento ela deve ser considerada como a continuao da linha anterior. Se uma linha comea na mesma coluna que a anterior, ento elas so consideradas definies independentes. Se uma linha comea mais atrs do que a anterior, ento essa linha no pretence mesma lista de definies. definies do mesmo gnero devem comear na mesma coluna exemplo :: Float -> Float -> Float exemplo x 0 = x exemplo x y = let a = x*y b = if (x>=y) then x/y else y*x c = a-b in (a+b)*c
2. 3. Ou seja:
Note que no estamos a criar tipos novos, mas apenas nomes novos para tipos j existentes. Esses nomes devem contribuir para a compreenso do programa. Exemplo:
Exemplo:
O tipo String outro exemplo de um tipo sinnimo, definido no Prelude. type String = [Char]
42
44
Operadores
Operadores infixos como o + , * , && , ... , no so mais do que funes. Um operador infixo pode ser usado como uma funo vulgar (i.e., usando notao prefixa) se estiver entre parentesis. Exemplo: Note que
(+) 2 3
equivalente a
2+3
Podem-se definir novos operadores infixos. (+>) :: Float -> Float -> Float x +> y = x^2 + y
equivalente a
ou a
Funes binrias podem ser usadas como um operador infixo, colocando o seu nome entre ` `. Exemplo: mod :: Int -> Int -> Int 3 `mod` 2 equivalente a mod 3 2 sig x | | | y x > y = 1 x < y = -1 otherwise = 0
Cada operador tem uma prioridade e uma associatividade estipulada. Isto faz com que seja possvel evitar alguns parentesis. Exemplo: x + y + z x + 3 * y equivalente a equivalente a (x + y) + z x + (3 * y)
Exemplo:
a x + b x + c
raizes :: (Double,Double,Double) -> (Double,Double) raizes (a,b,c) = (r1,r2) where r1 = (-b + r) / (2*a) r2 = (-b r) / (2*a) d = b^2 4*a*c r | d >= 0 = sqrt d | d < 0 = error raizes imaginarias error uma funo pr-definida que permite indicar a mensagem de erro devolvida pelo interpretador. Repare no seu tipo error :: String -> a
> raizes (2,10,3) (-0.320550528229663,-4.6794494717703365) > raizes (2,3,4) *** Exception: raizes imaginarias
46 48
possvel indicar a prioridade e a associatividade de novos operadores atravs de declaraes. infixl num op infixr num op infix num op
Listas
[T] o tipo das listas cujos elementos so todos do tipo T -- listas homogneas .
[3.5^2, 4*7.1, 9+0.5] :: [Float] [(253, Braga ), (22, Porto ), (21, Lisboa )] :: [(Int,String)] [[1,2,3], [1,4], [7,8,9]] :: [[Integer]]
Recorrncia
Como definir a funo que calcula o comprimento de uma lista ? Temos dois casos:
Se a lista fr vazia o seu comprimento zero. Se a lista no fr vazia o seu comprimento um mais o comprimento da cauda da lista.
a lista vazia [] o construtor (:), que um operador infixo que dado um elemento x de tipo a [] :: [a] (:) :: a -> [a] -> [a]
[1,2,3] uma abreviatura de 1:(2:(3:[])) que igual a 1:2:3:[] porque (:) associativa direita. Portanto: [1,2,3] = 1:[2,3] = 1:2:[3] = 1:2:3:[]
49
length [] = 0 length (x:xs) = 1 + length xs Esta funo recursiva uma vez que se invoca a si prpria (aplicada cauda da lista). A funo termina uma vez que as invocaes recursivas so feitas sobre listas cada vez mais curtas, e vai chegar ao ponto em que a lista vazia. length [1,2,3] = length (1:[2,3]) 1 1 1 1 3 + + + + length [2,3] (1 + length [3]) (1 + (1 + length [])) (1 + (1 + 0))
e uma lista l de tipo [a], constroi uma nova lista com x na 1 posio seguida de l.
Os padres do tipo lista so expresses envolvendo apenas os construtores : e [] (entre parentesis), ou a representao abreviada de listas. head (x:xs) = x tail (x:xs) = xs
> head [3,4,5,6] 3 > tail HASKELL ASKELL > head [] *** exception > null [3.4, 6.5, -5.5] False > soma3 [5,7] 13