Introdução ao Haskell
Introdução
•
Programação funcional é um paradigma de programação;
•
No paradigma imperativo, um programa é uma sequência de
instruções que mudam células na memória;
•
No paradigma funcional, um programa é um conjunto de definições
de funções que aplicamos a valores
;
•
Podemos programar num estilo funcional em muitas linguagens
Exemplos: Scheme, ML, O’Caml,
Haskell
, F#, Scala.
Introdução
O Quicksort em JavaIntrodução
•
Vantagens
•
Programas mais concisos;
•
Próximos de uma especificação matemática;
•
Excelente modularidade ( polimorfismo, ordem superior, lazy evaluation);
•
Praticamente todo componente é reutilizável (função);
•
Demonstrações de correção usando provas matemáticas;
•
A ordem de execução não afeta os resultados.
Introdução
•
Desvantagens
•
Compiladores/interpretadores mais complexos;
•
Difícil prever os custos de execução (tempo/espaço);
•
Alguns algoritmos são mais eficientes quando implementados de forma
imperativa.
Introdução (Histórico)
•
1930s
Alonzo Church desenvolve o cálculo-λ, um formalismo matemático
para exprimir computação usando funções;
•
1950s
Inspirado no cálculo-λ, John McCarthy desenvolve o LISP, uma das
primeiras linguagens de programação;
•
1970s
Robin Milner desenvolve o ML, a primeira linguagem funcional com
polimorfismo e inferência de tipos;
•
1970s–1980s
David Turner desenvolve várias linguagens que empregam
Introdução (Histórico)
•
1987
Um comitê acadêmico inicia o desenvolvimento do Haskell, uma
linguagem funcional lazy padronizada e aberta;
•
2003
Publicação do Haskell 98, uma definição padronizada da linguagem;
•
2010
Publicação do padrão da linguagem Haskell 2010.
Haskell
•
Uma linguagem funcional pura de uso genérico;
•
Nomeada em homenagem ao matemático americano Haskell B. Curry
(1900–1982);
•
Concebida para ensino e também para o desenvolvimento de aplicações
reais;
•
Resultado de mais de vinte anos de investigação por uma comunidade de
base acadêmica muito ativa;
•
Implementações abertas e livremente disponíveis:
http://www.haskell.org
Haskell (Aplicação)
Utilizações em backend de aplicações web:
•
Bump mover ficheiros entre smartphones
http://devblog.bu.mp/haskell-at-bump
•
Janrain plaforma de user management
http://janrain.com/blog/
•
Chordify extração de acordes musicais
http://chordify.net
Mais exemplos:
Haskell (Compiladores)
Hugs
•
Um interpretador interativo de Haskell;
•
Suporta Haskell 98 e bastantes extensões;
•
Para aprendizagem e desenvolvimento de pequenos programas;
Disponível em http://www.haskell.org/hugs
Haskell (Compiladores)
Glasgow Haskell Compiler (GHC)
•
Compilador que gera código-máquina nativo;
•
Suporta Haskell 98, Haskell 2010 e bastantes extensões;
•
Otimização de código, interfaces a outras linguagens, profilling,
grande conjunto de bibliotecas, etc;
•
Inclui também o interpretador ghci (alternativa ao Hugs)
Disponível em http://www.haskell.org/ghc
Haskell (Compiladores)
Linux/Mac OS: executar o hugs ou ghci
WinHugs
Utilizando o interpretador: > 2+3*5 17 > (2+3)*5 25 > sqrt (3^2 + 4^2) 5.0Haskell (Compiladores)
Alguns comandos básicos
Todo arquivo(script) em Haskell deve ter a extensão
.hs
Comando Significado
:load Carrea um arquivo para execução :reload Recarrega modificações
:edit Edita o arquivo atual
:type <expr> Mostra o tipo de uma expressão :help Obtem ajuda
:quit Termina a sessão
Haskell (operadores e funções aritméticas)
Operador/função Significado + Adição - Subtração * Multiplicação / Divisão^ Potência (expoente inteiro) div Quociente (divisão inteira) mod Resto (divisão inteira)
sqrt Raiz quadrada == Igualdade /= Diferença <, >, <=, >= Comparações
Haskell (convenções sintáticas)
•
Os argumentos de funções são
separados por espaços
Haskell (convenções sintáticas)
•
Um operador pode ser usando como uma função escrevendo-o entre
parêntesis;
•
Reciprocamente: uma função pode ser usada como operador
escrevendo-a entre crases.
(+) x y = x + y (*) 3 4 = 3 * 4 4 `mod` 2 = mod 4 2 f x `div` n = div (f x) n
Haskell (convenções sintáticas)
Identificadores
•
Os nomes de funções e argumentos devem
começar por letras
minúsculas
e podem incluir letras, dígitos, sublinhados e
apóstrofes:
fun1 x_2 y’ fooBar
•
As seguintes
palavras reservadas
não podem ser usadas como
identificadores:
case class data default deriving do else if import in
infix infixl infixr instance let module newtype of then
Haskell (convenções sintáticas)
Identação
•
Todas as definições num mesmo âmbito devem começar na mesma coluna:
Haskell (convenções sintáticas)
Comentários
•
Simples: começam por -- até ao final da linha
•
Várias linhas: delimitados por {- e -}
-- função para somar dois números soma x y = x + y
{- função desatualizada calc x = x `mod` 2 -}
Haskell (Prelude)
O módulo
Prelude
contém um grande conjunto de funções
pré-definidas:
•
operadores e funções aritméticas;
•
funções genéricas sobre listas e muitas outras.
O prelúdio-padrão é automaticamente carregado pelo
interpretador/compilador e pode ser usado em qualquer programa
Haskell.
> head [1,2,3,4] -- obter o 1o elemento1
> tail [1,2,3,4] -- remover o 1o elemento [2,3,4]
Haskell (Definindo funções)
> :edit> :load
> area_quadrado 10 20 > 200
Haskell (Definindo funções)
funcaoConstante = 12volume_paralelepipedo b a l = b*a*l soma x y = x + y
soma3 x y z = soma (soma x y) z media3 x y z = (soma3 x y z)/3
Exercício 01
--1) Faça uma função da média de 4 números sem utilizando utilizar -- o operador mais (+), utilize apenas as funções do slide anterior
--2) Faça uma função para calcular a hipotenusa de um triangulo --
retângulo-Haskell (Definições locais)
Podemos fazer definições locais usando
where
aos operadores e
funções:
A identação indica o âmbito das declarações; também podemos usar
agrupamento explícito:
Exercício 02
--1) Faça uma função sem parâmetros de entrada e que defina três
-- variáveis com valores fixos, a qual retorna “true” se os valores -- são iguais.
--2) Faça uma função para calcular a área de um triangulo com lados -- “a”, “b” e “c” utilizando a formula de Heron, mas crie uma -- função local para calcular o “S”.
Haskell (Tipos de dados)
Um
tipo
é um nome para uma coleção de valores relacionados
Escrevemos para indicar que a expressão “e” admite o tipo T.
•
Se e :: T, então o resultado de e será um valor de tipo T.
•
O interpretador
verifica
tipos indicados pelo programador e
infere
tipos
omitidos.
•
Os programas com erros de tipos são rejeitados antes da execução.
Haskell (Tipos de dados)
volume_paralelepípedo :: Int -> Int -> Int -> Int volume_paralelepipedo b a l = b*a*l
soma :: Float -> Float -> Float soma x y = soma x y
media3 :: Int -> Int -> Int -> Float media3 x y z = (x + y + z)/3
soma :: (Int,Int) -> Int soma (x,y) = x+y
Uma função faz corresponder valores de um tipo em valores de outro
um tipo
Haskell (Tipos de dados)
Tipo Descrição
Bool valores lógicos - True, False
Char carateres simples - 'A', 'B', '?', '\n'
String sequências de carateres - "Abba", "UB40"
Int inteiros de precisão fixa (32 ou 64-bits) ex: 142, -1233456 Integer inteiros de precisão arbitrária (limitados pela memória do
computador)
Float vírgula flutuante de precisão simples ex: 3.14154, -1.23e10 Double vírgula flutuante de precisão dupla
Haskell (Tipos de dados)
Uma
lista
é uma sequência de tamanho variável de elementos
de um mesmo tipo.
Haskell (Tipos de dados)
Uma
tupla
é uma sequência de tamanho fixo de elementos de
tipos possivelmente diferentes.
Em geral: (T1,T2,...,Tn) é o tipo de tuplas com n componentes de tipos
Ti para i de 1 a n.
Haskell (Tipos de dados)
Listas
de tamanhos diferentes podem ser do mesmo tipo.
Tuplas
de tamanhos diferentes têm tipos diferentes.
Os elementos de listas e tuplas podem ser quaisquer valores, inclusive
outras listas e tuplas.
['a'] :: [Char] ['b','a','b'] :: [Char] ('a','b') :: (Char,Char) ('b','a','b') :: (Char,Char,Char) [['a'], ['b','c']] :: [[Char]] (1,('a',2)) :: (Int,(Char,Int)) (1, ['a','b']) :: (Int,[Char])
Haskell (Tipos de dados)
Observações:
•
A lista vazia [] admite
qualquer
tipo de lista [T ]
•
A tupla vazia () é o
único valor
do tipo unitário ()
•
Não existem tuplas com apenas um elemento
Haskell (Tipos de dados)
Funções polimórficas:
•
Certas funções operam com valores de quaisquer tipos; tais funções
admitem
tipos com variáveis
.
•
Uma função diz-se
polimorfa
(“de muitas formas”) se admite um tipo
com variáveis.
A função length calcula o comprimento de uma lista de
valores de
qualquer tipo
a.
Haskell (Tipos de dados)
Funções polimórficas
•
Ao aplicar funções polimorfas, as variáveis de tipos são
automaticamente substituídas pelos tipos concretos:
As variáveis de tipo devem começar por uma letra minúscula; é
convencional usar a, b, c, . . .
> length [1,2,3,4] -- Int 4
> length [False,True] -- Bool 2
> length [(0,'X'),(1,'O’)] --(Int,Char) 2
Haskell (Tipos de dados)
Funções polimórficas
•
Certas funções operam sobre vários tipos mas não sobre quaisquer tipos:
> sum [1,2,3] 6 > sum [1.5, 0.5, 2.5] 4.5 > sum ['a', 'b', 'c'] ERRO
> sum [True, False] ERRO
Haskell (Tipos de dados)
Funções polimórficas
•
Nestes casos o tipo mais geral da função tem restrições de classe
:
•
“Num a => ...” é uma
restrição de classe
da variável a.
•
Indica que sum opera apenas sobre tipos a que sejam numéricos.
Haskell (Tipos de dados)
Funções polimórficas
•
Algumas classes são
:
•
Exemplos:
(+) :: Num a => a -> a -> a (/) :: Fractional a => a -> a -> a (==) :: Eq a => a -> a -> Bool (<) :: Ord a => a -> a -> Bool max :: Ord a => a -> a -> a Classe DescriçãoNum tipos numéricos (ex: Int, Integer, Float, Double) Integral tipos com divisão inteira (ex: Int, Integer)
Fractional tipos com divisão fracionária (ex: Float, Double) Eq tipos com igualdade
Haskell (Tipos de dados)
Funções polimórficas
Algumas classes respeitam uma hierarquia:
•
Ord é uma subclasse de Eq
•
Num é uma subclasse de Eq
•
Fractional e Integral são subclasses de
Num
Assim, podemos usar:
== e /= com tipos em Ord ou em Num
+, - e * com tipos em Fractional ou em
Integral
Em alguns casos será necessário converter elementos de uma classe em outra, exemplo:
Haskell (Expressões condicionais)
Podemos exprimir uma condição com duas alternativas usando
‘
if. . . then. . . else. . .
’.
As expressões condicionais podem ser encadeadas:
Em Haskell, ao contrário do C/C++/Java, a alternativa ‘else’ é
obrigatória
abs :: Float -> Float
abs x = if x >= 0 then x else -x
sinal :: Int -> Int
sinal x = if x > 0 then 1 else
Haskell (Expressões condicionais)
Podemos usar
guardas
em vez de expressões condicionais:
•
Testa as condições pela ordem no programa.
•
Seleciona a primeira alternativa verdadeira.
•
Se nenhuma condição for verdadeira: erro de execução.
•
A condição ‘otherwise’ é um sinónimo de True
sinal :: Int -> Int sinal x | x > 0 = 1
| x == 0 = 0
Haskell (Expressões condicionais)
Pode-se usar
múltiplas equações
encontrar uma solução:
Pode-se usar também
padrões
ou
variáveis anônimas
para melhorar a
solução acima
ou :: Bool -> Bool -> Bool ou False False = False ou True False = True ou False True = True ou True True = True
ou' :: Bool -> Bool -> Bool ou' False False = False ou' _ _ = True
Lista de Exercício 01
1) Faça uma função que calcule a distância entre dois pontos
2) Faça uma função para verificar se um ano informado é bissexto ou não.
3) Defina uma função que recebe três números inteiros representando, respectivamente, um dia, um mês e um ano e verifica se os números formam uma data válida.
4) Crie uma função par::Int->Bool para verificar se um numero é par ou impar. 5) Escreva a função conceito :: Float -> Char que recebe uma nota e retorne o conceito correspondentes conforme as regras abaixo:
Nota abaixo de 4 – Conceito E, Nota entre 4 e 5.99 conceito D, Nota entre 6 e 7.49 conceito C, Intervalo entre 7.5 e 8.99 conceito B e acima de 9 conceito A.
Haskell (Tuplas)
As
tuplas
permitem a definição e o uso de tipos de dados heterogêneos sob
uma estrutura relacional. As tuplas são representadas em scripts por listas
de componentes separados por vírgula, entre parênteses.
O Prelude fornece duas funções para retornar respectivamente o primeiro e
o segundo elemento:
Sua implementação provavelmente utiliza variáveis anônimas:
("JOSE",1.8,23) ou (100,10.4,”Brasil”)
fst :: (a,b) -> a ou snd :: (a,b) -> b
Haskell (Tuplas)
Pode-se utilizar
tuplas
e a palavra reservada
type
para definir tipos de
dados mais complexos:
Com uso de variáveis anônimas podemos buscar informações específicas nas
tuplas:
type Seq = String
type Nomes = (Seq, Seq, Seq, Seq) --funções constantes
f_nomes_estacoes :: Nomes f_nomes_estacoes =
("Inverno","Outono","Primavera","Verao")
primeiro :: Nomes -> Seq primeiro (x, _, _, _) = x segundo :: Nomes -> Seq segundo (_, x, _, _) = x
Exercício 03
--1) Crie um trecho de script que contenha a definição de tipo para nome, idade, peso e esporte praticado. Logo em seguida defina outro tipo de dados que represente pessoas com as quatro informações acima. --2) Crie uma função chamada bancoDeDados que recebe um “id” do tipo inteiro e retorne uma pessoa. Dentro dessa função faça uma instrução condicional para várias pessoas diferentes com identificadores
diferentes.
--3) Crie funções para recuperar o nome, a idade, o peso e o esporte preferido de um tipo pessoa.
--4) Crie uma função que recebe duas pessoas e retorne o nome da mais nova, faça uma função também para retornar a mais leve.
--5) No console, busque o nome da pessoa de código 4 que está na função que representa o banco de dados.
Haskell (Recursão)
Pode-se definir funções usando outras previamente definidas.
Contudo, pode-se definir uma função por recorrência, isto é, usando a
própria função que estamos a definir; tais definições dizem-se recursivas.
Exemplo:
fatorial :: Int -> Int fatorial 0 = 1 fatorial n = fatorial (n-1) * n --fatorial 5 --(fatorial 4) * 5 --((fatorial 3) * 4) * 5 --(((fatorial 2) * 3) * 4) * 5 --((((fatorial 1) * 2) * 3) * 4) * 5 --(((((fatorial 0) * 1) * 2) * 3) * 4) * 5 --((((1 * 1) * 2) * 3) * 4) * 5 --(((1 * 2) * 3) * 4) * 5 --((2 * 3) * 4) * 5 --(6 * 4) * 5 --24 * 5 --120
Haskell (Recursão)
Alternativas de implementação:
Utilizando guardar:
Utilizando condicional:
Porque recursão?
fatorial n | n==0 = 1 | otherwise = n * fatorial (n-1)fatorial n = if n==0 then 1 else n*fatorial (n-1)
• Exprimir a solução de um problema usando problemas semelhantes mas de menor tamanho. • Modelo universal de computação: qualquer algoritmo pode ser escrito usando funções recursivas. • Podemos demonstrar propriedades de funções recursivas usando indução matemática.
Haskell (Recursão)
Exemplos:
N-ésimo termo de fibonnacci:
Quantos números múltiplos de sete existem entre zero e um n-ésimo
número:
fibonacci :: Int -> Int fibonacci 0 = 0
fibonacci 1 = 1
fibonacci n = (fibonacci (n-1)) + (fibonacci (n-2))
multiplo7 :: Int -> Int multiplo7 7 = 1
multiplo7 n | n<=6 = 0
Exercício 04
--1) Crie uma função recursiva para calcular a potencia de um número.
--2) Crie uma função recursiva para dizer se um número é par. Essa função deve retornar True ou False.
--3) Crie funções recursiva para informar o valor de um somatório dado um número N.
--4) Com base na questão dois do Exercício 3 (slides anteriores), faça
funções recursivas que dado o “id” limite, deve-se percorrer todos os dados do banco de dados do primeiro até esse valor final e retorne:
a) Quantas pessoas foram selecionadas na base de dados b) Qual a menor idade da base
c) Qual a soma da idade das pessoas d) Qual a média de idade
e) Quantos pessoas estão acima da média de idade
5) somaDigitos: recebe um número natural e retorna a soma de seus dígitos. Ex.: somaDigitos 3284 ==> 17
Haskell (Listas)
•
Listas são coleções de elementos:
•
em que a
ordem é significativa
•
elementos de um
mesmo tipo
•
possivelmente com elementos repetidos
•
Uma lista em Haskell
ou é vazia [];
ou é x:xs (x seguido da lista xs)
[1, 2, 3, 4] == 1 : 2 : 3 : 4 : []
O operador :forma uma nova lista desde que que o último elemento seja uma
Haskell (Listas)
•
Pode-se concatenar listas do mesmo tipo com o operador
++
•
O tipo String na verdade é uma lista do tipo char: [Char]
•
Algumas funções do Prelude para manipular String
> ['O','l','a'] == "Ola" True
> [(1,'b'),(2,'c')] ++ [(3,'a'), (4,'w')] [(1,'b'),(2,'c'),(3,'a'),(4,'w')]
> words "O rato roeu a roupa do rei de roma"
["O","rato","roeu","a","roupa","do","rei","de","roma"] > unwords
["Um","prato","de","trigo","para","tres","tigres"] "Um prato de trigo para tres tigres"
Haskell (Listas)
Pode-se definir listas com
sequências aritméticas
da forma [a..b] onde
a e b são números:
Pode-se definir
listas infinitas
:
> [1..10] [1,2,3,4,5,6,7,8,9,10] > [1,3..10] [1,3,5,7,9] > [10,9..1] [10,9,8,7,6,5,4,3,2,1] take 10 [1,3..] [1,3,5,7,9,11,13,15,17,19] > [1,3..] !! 4 9
Haskell (Listas)
A manipulação das listas é muitas da vezes feita através de recursão
separando a cabeça e cauda:
produtorio :: [Int] -> Int produtorio [] = 1
produtorio (x:xs) = x * produtorio xs quantidade :: [a] -> Int
quantidade [] = 0
Exercício 05
--1) Crie uma função que retorne uma lista com todas as letras do alfabeto. --2) Crie uma função que retorne uma lista com os números de 0 a 200 de
forma decrescente.
--3) Crie uma função para retornar o inverso de uma lista.
--4) Crie uma função para pegar os “n” primeiros elementos de uma lista --5) Crie uma função para remover os “n” primeiros elementos de uma lista --6) Crie uma função que remove o último elemento de uma lista
--7) Crie uma função que remove os “n” últimos elementos de uma lista --8) Crie uma função que remove o n-ésimo elemento de uma lista
Haskell (Listas - Compressão)
•
Em matemática é usual definir conjuntos a partir de outro usando notação
em compreensão. Exemplo:
o qual define o conjunto:
•
Em Haskell podemos definir uma lista a partir de outra usando uma
notação semelhante. Exemplo:
> [x^2 | x<-[1,2,3,4,5]] [1, 4, 9, 16, 25]
Haskell (Listas - Compressão)
•
Um termo “padrão<-lista” chama-se um
gerador
:
•
determina os valores das variáveis no padrão;
•
determina a ordem dos valores gerados.
Exemplo:
•
Pode-se usar múltiplos geradores:
> [x | x <-[1..10] ] [1,2,3,4,5,6,7,8,9,10] > [x | x <-[1,4..300] ] [1,4,7,10,13,16,19,22,25,28] > [x | x <-[1,7..20] ] [1,7,13,19] > [(x,y) | x<-[1,2,3], y<-[4,5]] [(1,4),(1,5),(2,4),(2,5),(3,4),(3,5)]
Haskell (Listas - Compressão)
•
Ordem entre geradores:
•
As variáveis dos geradores posteriores mudam primeiro;
•
Analogia: ciclos ‘for’ embutidos:
> [(x,y) | x<-[1,2,3], y<-[4,5]] -- x primeiro [(1,4),(1,5),(2,4),(2,5),(3,4),(3,5)]
> [(x,y) | y<-[4,5], x<-[1,2,3]] -- y primeiro [(1,4),(2,4),(3,4),(1,5),(2,5),(3,5)]
Haskell (Listas - Compressão)
•
Dependências entre geradores:
•
Um exemplo: a função concat (do prelúdio-padrão) concatena
uma lista de listas, exemplo:
Podemos definir usando uma lista em compreensão:
> [(x,y) | x<-[1..3], y<-[x..3]]
[(1,1),(1,2),(1,3),(2,2),(2,3),(3,3)]
Os geradores podem depender dos valores anteriores mas não
dos posteriores.
> concat [[1,2,3],[4,5],[6,7]] [1,2,3,4,5,6,7]
concat :: [[a]] -> [a]
Haskell (Listas - Compressão)
•
Guardas
As definições em compreensão podem incluir condições (designadas
guardas) para filtrar os resultados.
Exemplo: Conjunto dos inteiros x, tal que x está entre 1 e 10 e x é par.
Divisores de um num número inteiro positivo:
> [x | x<-[1..10], x`mod`2==0] [2,4,6,8,10]
divisores :: Int -> [Int]
Haskell (Listas - Compressão)
•
Exemplos:
--Um número é primo se é divisível por um e por eleprimo :: Int -> Bool primo n = divisores n == [1,n]--Vamos verificar se uma lista está em ordem crescente zipar (a:as) (b:bs) = (a,b) : zipar as bs
zipar _ _ = []
pares :: [a] -> [(a,a)]
pares xs = zipar xs (tail xs) crescente :: Ord a => [a] -> Bool
crescente xs = and [x<=x' | (x,x')<-pares xs]
--Listar todos os números primos entre 2 e N primos :: Int -> [Int]
primos n = [x | x<-[2..n], primo x]
Combina duas listas na lista dos pares de elementos
correspondentes. Exemplo: > pares [1,2,3,4] [(1,2),(2,3),(3,4)] Usasmos a função and do Prelude.
Haskell (Listas - Compressão)
•
Exemplos:
--Procurar um valor numa lista e obter todos os seus índices indices :: Eq a => a -> [a] -> [Int]
indices x ys = [i | (i,y)<-zip [0..n] ys, x==y] where n = length ys - 1
--Rescreva a função anterior com as funções do módulo Char minusculass :: String -> Int
minusculass cs = length [c | c<-cs, isLower c]
--Retornar quantas letras de uma string são minúsculas minusculas :: String -> Int
minusculas txt = length [c | c<-txt, c>='a' && c<='z']
No início do arquivo digite: Import Char
--Escreva uma função para converter uma String para maiúscula stringUpper :: String -> String
stringUpper cs = [toUpper c | c<-cs]
--Escreva uma função para escrever uma string com condição.
Exercício 06
--1) Crie uma função que recebe dois parâmetros: uma lista de inteiros e um número que representa a operação sobre a lista (1-lista de números pares e 2-lista de números impares). OBS: use as funções odd e even.
--2) Crie uma função que recebe uma lista de tuplas do tipo (Int,Int) e retorne uma lista com a soma das tuplas. Ex: [(1,2),(4,7)] => [3,11].
--3) Crie uma função que recebe duas listas e retorne outra lista com a combinação de todos os elementos em pares.Ex [1,2] [3] => [(1,3),(2,3)] --4) Crie uma função que remove um determinado caractere de uma string
--5) Crie uma função que retorne o código dos alunos que tenham nota acima de oito, essa função deve manipular o retorno da função: baseDeDados = [ (1, “André”, 10.0), (2, “Carlos”, 6.8), (3,”Maurício”, 7.0)]
--6) Crie uma função que recebe uma lista com vários nomes e verifica se alguns desses nomes começam com o parâmetro de entrada N.
--7) Para calcular as combinações de notas para devolver o troco durante um pagamento, podemos definir a função
Haskell (Listas)
•
Por causa da
lazy evaluation
as listas são calculadas à medida da
necessidade e apenas até onde for necessário.
•
Algumas funções do prelúdio-padrão produzem especificamente listas
infinitas:
-- a lista infinita 1,1,.. uns :: [Int] uns = 1 : uns > head (uns) repeat :: a -> [a] -- repeat x = [x,x,x,...] cycle :: [a] -> [a]-- cycle xs = xs++xs++xs++... > take 10 (repeat 1)
Haskell (Listas)
Porquê usar listas infinitas?
Permite simplificar o processamento de listas finitas combinando-as com
listas infinitas (por ex.: evita especificar comprimento).
•
Um exemplo simples: escrever uma função preencher :: Int -> String ->
String que preenche uma cadeia com espaços de forma a perfazer n
caracteres.
•
Solução:
preencher n xs = take n (xs++repeat ' ') > preencher 10 "Haskell"
"Haskell "
> preencher 10 "Haskell B. Curry" "Haskell B."
Haskell (Listas)
Pode-se converter valores de qualquer tipo para String com a função
show
:
Pode-se também converter valores de Strting para Fractional através da
função
read
:
Uma função útil é a
putStr
, ela imprime uma String no console
> show 98 "98“
> read "77" + 3 80
> putStr " 2 tabs uma\t\tsaída" 2 tabs uma saída
Caractere Efeito \t Tabulação \n Nova linha \\ Imprime barra \’ Imp. aspas simples \” Imp. aspas duplas > show "Ola"
"\"Ola\"" > " 7 ao quadrado "++ show (7*7)“7 ao quadrado 49"
> putStr "Saltando\nde linha" Saltando
Haskell (Listas)
Algumas funções
Haskell (Funções de alta ordem)
•
Uma função é de
ordem superior
se tem um argumento que é
uma função ou um resultado que é uma função.
Exemplo: o primeiro argumento de twice é uma função:
twice :: (a -> a) -> a -> a twice f x = f (f x) dobra x = 2 * x Triplica x = 3 * x > twice dobra 10 40 > twice triplica 10 90
Agora funções são consideradas com os mesmos direitos de qualquer outro tipo,
assim elas podem ser passadas por parâmetro ou podem ser retornadas como resultado de uma outra função.
Haskell (Funções de alta ordem)
Outro exemplo:
•
Vantagens:
•
Permite definir
padrões de computação
comuns que podem ser facilmente
reutilizados;
•
Facilita a definição de
bibliotecas para domínios específicos.
app :: (a->b) -> (a,a) -> (b,b) app f (x,y) = (f x, f y)
> app chr (65, 70) ('A','F’)
> app tan (65, 70)
Haskell (Funções de alta ordem)
A função
mult
abaixo, pode ser entendida como tendo dois argumentos de
entra e um de retorna , Mas na realidade
mult
é uma função que recebe um
argumento do tipo Int e devolve uma função do tipo (Int->Int).
Em Haskell, todas as funções são unárias!
mult 2 5 ≡ (mult 2) 5 :: Int
•
Assim, mult pode ser usada pra gerar novas funções:
mult :: Int -> Int -> Int mult x y = x * y
Curring
: aplicação de funções parciais
dobrar = mult 2 triplicar = mult 3 > dobrar 30
60
Haskell (Funções de alta ordem)
Os
operadores infixados
também podem ser aplicados a apenas um
argumento, gerando assim uma nova função.
Exemplos:
(<=) :: Integer -> Integer -> Bool > (0<=) 10
True
(+) :: Integer -> Integer -> Integer > (5+) 9
14
(*) :: Double -> Double -> Double > (3*)7
Haskell (Funções de alta ordem)
A maioria das definições sobre listas se encaixam em três casos:
•
folding colocação de um operador entre os elementos de uma lista;
•
filtering filtra alguns elementos da lista;
•
mapping a aplicação de funções a todos os elementos da lista.
Exemplo de aplicação de
high order functions
Haskell (Funções de alta ordem)
Veja que as funções abaixo tem um padrão de computação:
Para esse padrão existe a seguinte definição:
Utilizando a função de alta ordem temos:
Folding
sum [] = 0
sum (x:xs) = x + sum xs and [] = Trueand (x:xs) = x && and xs
conc [] = []
conc (x:xs) = x ++ conc xs
> foldr (+) 0 [1..5]
15 > foldr (&&) False []False
concat l = foldr (++) [] l > concat [“Hi, ”,”man”]
Padrão, aplicamos um operador entre cada elemento de uma lista
Haskell (Funções de alta ordem)
Veja que as funções abaixo tem um padrão de computação:
Para esse padrão existe a seguinte definição:
Utilizando a função de alta ordem temos:
Maping
ft [] = [] ft (x:xs) = fatorial x : ft xs min [] = [] min (x:xs) = toLower x : min xs trip [] = [] trip (x:xs) = (3*x) : trip xs > map fatorial [1..5] [1,2,6,24,120] > map toLower [‘X’,’B’]“xb” > map (3*) [1..5] [3,6,9,12,15]Padrão, aplicamos uma função a cada elemento da lista
digit [] = []
digit (x:xs) | isDigit x = x:digit xs | otherwise = digit xs
Haskell (Funções de alta ordem)
Veja que as funções abaixo tem um padrão de computação:
Para esse padrão existe a seguinte definição:
Utilizando a função de alta ordem temos:
Filtering
Padrão, filtramos elementos da lista de acordo com uma
função de critério. aprov [] = [] aprov (x:xs) = if x>=6 then x:aprov xs else aprov xs > filter (6<=) [2,9,10,5,9] [9,10,9]
> filter isDigit "abc123cdf" "123"
Haskell (Funções de alta ordem)
•
Alonzo Church inventou um sistema formal, chamado λ-calculus (Lambda
Cálculo) , e definiu a noção de função computável utilizando este sistema.
•
O Lambda Cálculo pode ser chamado “a menor linguagem de programação
universal” do mundo. As linguagens funcionais são baseadas nesse conceito.
•
O lambda cálculo pode ser visto como uma linguagem de programação
abstrata em que funções podem ser combinadas para formar outras
funções, de uma forma pura.
•
Como dito anteriormente, o lambda cálculo trata funções como cidadãos de
primeira classe, isto é, entidades que podem, como um dado qualquer, ser
utilizadas como argumentos e retornadas como valores de outras funções.
Funções anônimas (Conceitos)
Haskell (Funções de alta ordem)
Uma abstração lambda é um tipo de expressão que denota uma função:
O λ determina que existe uma função, e é imediatamente seguido por uma
variável, denominada parâmetro formal da função.
Abstração Lambda:
Em Haskell:
Funções anônimas (Conceitos)
(λx. + x1)
λy. π * y * y
> (\y -> pi * y * y) 3 28.2743338823081
Haskell (Funções de alta ordem)
Em Haskell, é possível definir novas funções inspiradas no conceito de
abstrações lambda (λ)
da forma:
Exemplos:
Funções com mais de uma parâmetro são da forma:
Exemplos:
Funções anônimas
\x -> e Representa uma função com
parâmetro x e corpo “e”
\x … pn -> e > (\x -> x+x) 5 10 > (\y -> y*y) 416 > (\x -> x:x^2:[]) 2 [2:4] > (\(x:xs) y-> y:xs) [1,2,3,4,5,6] 7
[7,2,3,4,5,6] > (\(x1,y1) (x2,y2) -> (x1,y2)) (0,3) (6,9)(0,9)
Haskell (Funções de alta ordem)
É possível utilizar funções anônimas na definição de outras funções:
Exemplos:
As funções anônimas são úteis para evitar a declaração de funções auxiliares.
Exemplos:
Operadores infixados sob um argumento são na verdade funções anônimas:
Funções anônimas
dobro = \x -> x+x > dobro 5 10 cauda = \(_:c) -> c > calda [1,2,3,4] [2,3,4]trocaPares xs = map troca xs
where troca (x,y) = (y,x)
≡
trocaPares1 xs = map (\(x,y) -> (y,x)) xsHaskell (Funções de alta ordem)
Exemplo:
Funções anônimas
-- Nº Aluno, Nome, Nota
type Aluno = (Int, String, Float) type Curso = [Aluno]
listaAlunos :: Curso
listaAlunos = [(1234, "Jose Azevedo", 13.2), (2345, "Carlos Silva", 9.7),(3456, "Rosa Mota", 17.9)]
mediaDasNotas :: Curso->Float
mediaDasNotas lista = (/) (sum (map (\(_,_,n)->n) lista)) (fromIntegral (length lista)) > mediaDasNotas listaAlunos
Exercício 07
--1) Usando funções de alta-ordem, crie uma função ou um comando no prompt que receba uma lista de strings e retorne uma lista com o tamanho de cada string da lista de entrada.
--2) Crie uma função de alta ordem chamada takeWhile, a qual recebe uma função de filtragem e uma lista, o objetos é retorna a lista de acordo com a filtragem. --3) a) Implementar uma função que calcula o sucessor de um número inteiro
usando expressão lambda (λx. x+1). Em seguida, b) definir uma função duasVezes para aplicar uma função nela mesma. Finalmente, c) construir uma função para mapear a aplicação de sucessor e d) duasVezes sobre uma lista de inteiros. Faça isso de forma recursiva, com compreensão de lista e com uma função de alta
ordem. e) Por fim, dê dois exemplos do uso das funções de mapeamento com funções lambdas.
--4) Usando o conceito de curring e a função abaixo, crie uma função chamada “incrementa”, a qual irá incrementar o valor passado como parâmetro em 1. soma :: Int -> Int -> Int
Exercício 07
--5) Usando o mesmo conceito de curring do exercício anterior, faça uma função que incremente os elementos de uma lista de inteiros.
--6) Utilizando map, crie funções que convertas os valores da esquerda nos da direita:
--[0,1,4,9] -> [0.0,1.0,2.0,3.0] --"HAL" -> "IBM"
--["bom","dia","turma"] -> "bdt" --["ciênca", "da", "computação"] -> [6,2,10]
--7) Crie tipos para representar Pessoa, Livro e uma lista de livros alugados pelas pessoas. Depois crie funções da forma:
a) emprestadosPorPessoa :: Database -> Person -> [Book] b) emprestadosPorLivro :: Database -> Book ->[Person] c) estaEmprestado :: Database -> Book -> Bool
Haskell (IO)
Um programa Haskell está organizado em módulos;
Cada módulo é uma coleção de funções e tipos de dados definidos em um
ambiente privado
Um módulo pode exportar todas ou determinadas definições:
Ao iniciar o interpretador, o módulo Prelude já é carregado para execução.
Módulos
module Nome (definições_a_serem_exportadas) where <importações>
Haskell (IO)
Depois que um módulo é carregado pelo comando load, as definições
disponíveis passam a estar disponíveis no ambiente junto com o Prelude;
Módulos
module Temp where
cel2Far c = c * 1.8 + 32 kel2Cel k = k - 273
kel2Far k = cel2Far (kel2Cel k)
> :load "C:\\Program Files (x86)\\WinHugs\\packages\\hugsbase\\Temp.hs"
module Exemplo where import Char
letra :: Int -> Char
letra n = if (n>=65 && n<=90)||(n>=97 && n<= 122) then chr n
else ' '
Por default, se nada for indicado para exportação,
todas as definições serão públicas.
Pode-se importar somente certa função do módulo: import Char (chr, toLower)
Haskell (IO)
•
Para criar um programa executável com o compilador Haskell, precisa-se de
um módulo principal com uma função main, a qual retorna um tipo IO;
•
A função main é o ponto de entrada no programa, pois é ela que é invocada
quanto o programa comilado é executado
•
A compilação usando GHC, pode ser feita executando o seguinte comando
no shell:
Compilando programas Haskell
module Main where main :: IO()
main = putStr "Olá mundo!!"
Haskell (IO)
•
Haskell, como as outras linguagens de programação, possui funções
que se comunicam com o sistema operacional, afim de realizar entrada
e saída de dados.
•
Tais funções devem retornar um valor do tipo (IO t), aonde t significa
um tipo de dado qualquer.
•
Interação com usuários e bloco de comandos
putStr :: String IO() putStrLn :: String IO() getLine :: IO(String)
Os parênteses é um tipo de dado que ter apenas um valor, o qual significa a
ausência de dados.
Nesse caso a operação de entrada e saída retorna uma String.
Haskell (IO)
•
Um programa é compostos por um conjunto de ações que podem
acionar diversas funções, para dar suporte a essa necessidade, o
Haskell usa o conceito de blocos de ação, através do comando
do
:
•
Interação com usuários e bloco de comandos
menu :: IO()
menu = do putStrLn "1-Incluir cliente" putStrLn "2-Alterar cliente" putStrLn "3-Excluir cliente" putStrLn "0-Sair"
imprimeString :: String -> IO() imprimeString [] = return ()
imprimeString (x:xs) = do putChar x
imprimeString xs
Essa palavra não retorna um valor e sim converte um valor do tipo “a” em um tipo
Haskell (IO)
•
As funções getChar e getLine pode trabalhar com o operador “<-” para
atribuir o valor capturado do teclado para uma função:
•
Interação com usuários e bloco de comandos
concatenalinhas :: IO()
concatenalinhas = do str1 <- getLine str2 <- getLine
putStrLn (str1 ++ str2)
ask :: String -> IO String
ask question = do putStr question getLine
principal :: IO ()
principal = do nome <- ask "Qual é o seu nome? " matr <- ask "Qual é sua matrícula? " putStrLn ("Bem-vindo "++ nome ++ "!") putStrLn ("Seu número é "++ matr)
Haskell (IO)
•
Outros exemplos:
Interação com usuários e bloco de comandos
lerString :: IO String lerString = do x <- getChar if x == '\n' then return [] else do xs <-lerString return (x:xs) executar :: IO() executar = do x <- getLine
let a = map toUpper x b = map toLower x putStr a
putStr "\n" putStr b
O comando let define funções locais.
calcular :: IO()
calcular = do putStr "Digite um numero: " n <- getLine
let x = ((read n)::Double) s = sin x
c = cos x
putStr ("O seno de "++n++" e' "++(show s)++['.','\n']++ "O cosseno de "++n++" e' "++(show c)++['.','\n'])
Haskell (IO)
•
Lendo e escrevendo outros tidos de dados (readLn e print):
Interação com usuários e bloco de comandos
leInt :: IO(Int)
leInt = do putStr "Digite um valor inteiro: " readLn
leInt2 :: IO(Int)
leInt2 = do putStr "Digite um valor inteiro: " n <- getLine return (read n) Transforma em String soma :: IO () soma = do n1 <- leInt n2 <- leInt
putStr "A soma e': " print (n1+n2)
As funções read, readLn e print possuem as seguintes assinaturas:
lista :: IO ()
lista = do putStr "Digite um valor: " x <- readLn
Haskell (IO)
•
Outro exemplo:
Interação com usuários e bloco de comandos
menuAdivinhe = do num <- randomRIO(1::Int,100)putStrLn "Gerando numero entre 1 e 100 ..." adivinhe1 num
adivinhe1 num = do putStr "Tente descobrir (digite um numero):" numP <- getLine
if (read numP) < num
then do putStrLn "Muito baixo" adivinhe1 num
else if (read numP) > num
then do putStrLn "Muito alto" adivinhe1 num
Haskell (IO)
•
Outro exemplo (while):
Interação com usuários e bloco de comandos
main = do nomes <- leNomes
putStr (unlines (sort nomes)) leNomes = do putStr ("Escreva um nome: ")
nome <- getLine if nome == ""
then return []
else do nomes <- leNomes
return ([nome] ++ nomes) sort [] = []
sort (a:b) = sort [x | x <- b, x<a] ++ [a] ++
sort [x | x <- b, x>= a] le_imprime :: IO()
le_imprime = do entrada <-getLine if entrada == []
then return ()
else do putStrLn entrada le_imprime
Funão que recebe uma lista de strings, e a transforma em
uma string colocando o caracter de nova linha (‘\n’)
Exercício8
--1) Escreva um programa que lê uma linha, a partir do teclado, verifica se ela contém apenas caracteres alfabéticos e imprime essa linha na tela, com as
palavras em ordem inversa. Caso a linha contenha algum caractere não alfabético, imprime uma mensagem de erro.
--2) Escreva um programa que pergunta ao usuário o seu nome e telefone e imprime na tela a informação obtida, em uma única linha.
--3) Escreva um programa que lê várias linhas a partir do teclado, e imprime cada linha lida, com os caracteres convertidos para maiúsculas, até que seja digitada uma linha nula.
--4) Defina uma função leIntList que lê para uma sequência de valores inteiros do dispositivo de entrada padrão, até que seja digitado o valor 0, e retorna a lista dos valores lidos.
--5) Refaça o exercício anterior, supondo que os números devem ser digitados todos em uma única linha.
--6) Defina um programa que lê uma valor inteiro positivo n e imprime a lista de pares (i, i2), para valores de i no intervalo 1 ≤ i ≤ n.
Referencias
Livros
Material online
Slides do professor Pedro Vasconcelos DCC/FCUP http://www.dcc.fc.up.pt/~pbv/aulas/pf/