Progfunc PDF
Progfunc PDF
2016–2
Sumário
2
5 Expressão Condicional 5-1
5.1 Expressão condicional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-1
5.2 Definição de função com expressão condicional . . . . . . . . . . . . . . . . . . . 5-2
5.3 Equações com guardas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-3
5.4 Definições locais e guardas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-5
5.5 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-6
5.6 Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-9
3
9.7 Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-8
11 Sobrecarga 11-1
11.1 Sobrecarga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-1
11.2 Algumas classes de tipo pré-definidas . . . . . . . . . . . . . . . . . . . . . . . . 11-2
11.2.1 Eq . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-2
11.2.2 Ord . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-3
11.2.3 Enum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-3
11.2.4 Bounded . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-3
11.2.5 Show . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-3
11.2.6 Read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-4
11.2.7 Num . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-4
11.2.8 Real . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-4
11.2.9 Integral . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-5
11.2.10Fractional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-5
11.2.11Floating . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-5
11.2.12RealFrac . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-6
11.2.13RealFloat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-6
11.3 Sobrecarga de literais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-7
11.4 Conversão entre tipos numéricos . . . . . . . . . . . . . . . . . . . . . . . . . . 11-7
11.5 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-8
11.6 Inferência de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-9
11.7 Dicas e Sugestões . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-9
11.8 Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-11
4
13 Valores Aleatórios 13-1
13.1 Instalação do pacote random . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13-1
13.2 Valores aleatórios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13-1
13.3 Jogo: adivinha o número . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13-2
13.4 Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13-8
5
16 Argumentos da Linha de Comando e Arquivos 16-1
16.1 Argumentos da linha de comando . . . . . . . . . . . . . . . . . . . . . . . . . . 16-1
16.2 Encerrando o programa explicitamente . . . . . . . . . . . . . . . . . . . . . . . 16-2
16.3 Formatando dados com a função printf . . . . . . . . . . . . . . . . . . . . . . 16-4
16.4 Arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16-5
16.5 As funções lines e unlines, e words e unwords . . . . . . . . . . . . . . . . . 16-6
16.6 Exemplo: processar notas em arquivo . . . . . . . . . . . . . . . . . . . . . . . . 16-7
16.7 Problemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16-8
16.8 Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16-10
6
1 Paradigmas de Programação
Resumo
Ao desenvolver uma aplicação o programador segue uma visão de como o pro-
grama será executado, norteando a estruturação do seu código. Isto é o que chama-
mos de paradigma de programação.
Existem alguns paradigmas muito usados como a programação procedimental, a
programaçào orientada a objetos, a programação funcnional e a programação lógica.
Cada um deles tem uma visão diferente da estrutura e execução dos programas.
Sumário
1.1 Paradigmas de programação . . . . . . . . . . . . . . . . . . . . . . . . . . 1-1
1.2 Técnicas e paradigmas de programação . . . . . . . . . . . . . . . . . . . . 1-2
1.3 Categorias: programação imperativa e declarativa . . . . . . . . . . . . . . 1-2
1.3.1 Programação imperativa . . . . . . . . . . . . . . . . . . . . . . . . . 1-2
1.3.2 Programação declarativa . . . . . . . . . . . . . . . . . . . . . . . . . 1-2
1.4 Programação funcional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-3
1.4.1 Exemplo: quick sort em C . . . . . . . . . . . . . . . . . . . . . . . . 1-3
1.4.2 Exemplo: quick sort em Haskell . . . . . . . . . . . . . . . . . . . . . 1-3
1.5 A Crise do Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-4
1.6 Algumas características de Haskell . . . . . . . . . . . . . . . . . . . . . . . 1-4
1.7 Antecedentes históricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-4
1.8 Algumas empresas que usam Haskell . . . . . . . . . . . . . . . . . . . . . 1-7
1.9 Curso online de Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-7
• Por exemplo:
1-1
• Por exemplo:
• Esse é um dos motivos pelos quais novos paradigmas são considerados mais rígidos que
estilos tradicionais.
• Apesar disso, evitar certos tipos de técnicas pode facilitar a prova de correção de um sis-
tema, podendo até mesmo facilitar o desenvolvimento de algoritmos.
• Muito parecidos com o comportamento imperativo das linguagens naturais que expressam
ordens, programas imperativos são uma sequência de comandos para o computador exe-
cutar.
• O nome do paradigma, imperativo, está ligado ao tempo verbal imperativo, onde o progra-
mador diz ao computador: faça isso, depois isso, depois aquilo...
• Exemplos: paradigmas
• Exemplos: paradigmas
1-2
1.4 Programação funcional
do {
while ((l < h) && (a[l] <= p))
l = l+1;
while ((h > l) && (a[h] >= p))
h = h-1;
if (l < h) {
t = a[l];
a[l] = a[h];
a[h] = t;
}
} while (l < h);
a[hi] = a[l];
a[l] = p;
qs [] = []
qs (x:xs) = qs (filter (<x) xs) ++
[x] ++
qs (filter (>x) xs)
Observações:
• x:xs denota uma lista não vazia cuja cabeça é x e cuja cauda é xs.
• Uma lista pode ser escrita enumerando os seus elementos separados por vírgula e colocados entre colchetes.
• A sintaxe para aplicação de função consiste em escrever a função seguida dos argumentos, separados por
espaços, como em max 10 (2+x).
• A função filter seleciona os elementos de uma lista que satisfazem uma determinada propriedade.
• (<x) e (>x) são funções que verificam se o seu argumento é menor ou maior, respectivamente, do que x.
São funções anônimas construídas pela aplicação parcial dos operadores < e >.
1-3
1.5 A Crise do Software
• Linguagens declarativas (incluindo linguagens funcionais):
– permitem que programas sejam escritos de forma clara, concisa, e com um alto nível de abstração;
– suportam componentes de software reutilizáveis;
– incentivam o uso de verificação formal;
– permitem prototipagem rápida;
– fornecem poderosas ferramentas de resolução de problemas.
As linguagens funcionais oferecem um quadro particularmente elegante para abordar estes ob-
jetivos.
• Tipagem estática
• Avaliação lazy
Alonzo Church desenvolve o cálculo lambda, uma teoria de funções simples, mas poderosa.
• Década de 1950:
1-4
John McCarthy desenvolve Lisp, a primeira linguagem funcional, com algumas influências do cálculo lambda,
mas mantendo as atribuições de variáveis.
• Década de 1960:
Peter Landin desenvolve ISWIM, a primeira linguagem funcional pura, baseada fortemente no cálculo lambda,
sem atribuições.
• Década de 1970:
John Backus desenvolve FP, uma linguagem funcional que enfatiza funções de ordem superior e raciocínio
sobre programas.
• Década de 1970:
Robin Milner e outros desenvolvem ML, a primeira linguagem funcional moderna, que introduziu a inferência
de tipos e tipos polimórficos.
1-5
David Turner desenvolve uma série de linguagens funcionais com avaliação lazy, culminando com o sistema
Miranda.
• 1987:
• 2003:
O comitê publica o relatório Haskell 98, a definição de uma versão estável da linguagem Haskell.
• 2009:
O comitê publica o relatório Haskell 2010, uma revisão da definição da linguagem Haskell.
1-6
1.8 Algumas empresas que usam Haskell
• Exemplos de empresas que usam Haskell:
• Universidade de Glasgow
• Início: 19 de setembro de 2016
• Duração: 6 semanas
• https://www.futurelearn.com/courses/functional-programming-haskell
• Quem apresentar o certificado de participação no final do semestre poderá ganhar 1 ponto extra!
1-7
2 Ambiente de Desenvolvimento Haskell
Resumo
As atividades de programação serão desenvolvidas usando a linguagem funcional Haskell1 .
Nesta aula o aluno irá se familiarizar com o ambiente de programação em Haskell através da
avaliação de expressões no ambiente interativo, e edição e compilação de programas. Também ele
irá aprender a fazer suas primeiras definições de função.
Sumário
2.1 Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-1
2.2 Instalação do ambiente de desenvolvimento . . . . . . . . . . . . . . . . . . 2-1
2.2.1 Instalação do compilador de Haskell . . . . . . . . . . . . . . . . . . . 2-2
2.2.2 Instalação do editor Atom . . . . . . . . . . . . . . . . . . . . . . . . 2-3
2.2.3 Outros editores de texto . . . . . . . . . . . . . . . . . . . . . . . . . 2-6
2.3 O ambiente interativo GHCi . . . . . . . . . . . . . . . . . . . . . . . . . . 2-6
2.4 Bibliotecas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2-9
2.1 Haskell
Haskell é uma linguagem de programação funcional pura avançada. É um produto de código aberto de mais
de vinte anos de pesquisa de ponta que permite o desenvolvimento rápido de software robusto, conciso e correto.
Com um bom suporte para a integração com outras linguagens, concorrência e paralelismo integrados, depuradores,
ricas bibliotecas, e uma comunidade ativa, Haskell pode tornar mais fácil a produção de software flexível, de alta
qualidade, e de fácil manutenção.
GHC (Glasgow Haskell Compiler)2 é um compilador de código aberto para a linguagem Haskell, disponível para
diversas plataformas, incluindo Windows e diversas variedades de Unix (como Linux, Mac OS X e FreeBSD). GHC
é a implementação de Haskell mais usada.
GHC compreende um compilador de linha de comando (ghc) usado para compilar programas gerando código
executável, e também um ambiente interativo (GHCi), que permite a avaliação de expressões de forma interativa,
muito útil para testes durante o desenvolvimento.
A Plataforma Haskell 3 é um ambiente de desenvolvimento abrangente e robusto para a programação em
Haskell. Ela é formada pelo compilador GHC e por várias bibliotecas adicionais prontas para serem usadas.
• GHC, o compilador de Haskell mais usado atualmente, que oferece também o ambiente interativo GHCi.
1
Veja http://www.haskell.org/.
2
Veja http://www.haskell.org/ghc/.
3
Veja http://www.haskell.org/platform/.
2-1
• cabal-install4 , um pacote que fornece a ferramenta de linha de comando cabal que simplifica o processo
de gerenciamento de software Haskell, automatizando a busca, configuração , compilação e instalação de
bibliotecas e programas Haskell.
Atom é um editor de texto de código livre e de código aberto desenvolvido pelo GitHub, com suporte para plugins.
Atom é uma aplicação desktop construída usando tecnologias web.
É interessante que a Plataforma Haskell seja instalada em seu computador para facilitar o desenvolvimento
das atividades de programação deste curso. Ela pode ser instalada a partir do site https://www.haskell.org/
platform/, que disponibiliza programas de instalação para algumas plataformas incluindo Windows.
A Plataforma Haskell para Windows inclui:
• cabal-install, e
• bibliotecas adicionais.
4
Veja https://github.com/haskell/cabal/blob/master/cabal-install/README.md.
5
Veja https://atom.io/.
2-2
Instalação do GHC no Ubuntu
Para instalar o GHC sugerimos usar o repositório hvr/ghc (que dispnibiliza a última versão) para instalar os
pacotes ghc e cabal-install. No terminal execute os comandos:
2-3
Atom poderá ser atualizado automaticamente quando uma nova versão estiver disponível.
Configuração do Atom
Para facilitar o desenvolvimento de aplicações Haskell usando o Atom é necessária a instalação de alguns plu-
gins. Estes plugins podem dependender de algumas ferramentas externas relacionados a Haskell:
• stylish-haskell,
• ghc-mod, e
• hlint.
Para instalá-las execute os comandos seguintes no terminal (prompt de comandos):
cabal update
cabal install stylish-haskell
cabal install hlint
cabal install ghc-mod
Estes procedimentos poderão ser demorados, pois estas ferramentas serão compiladas e instaladas no computador.
Finalmente instale os plugins para Atom:
• language-haskell
• haskell-ghc-mod
• ide-haskell-cabal
• ide-haskell
2-4
• autocomplete-haskell
• ide-haskell-repl
• haskell-debug
Os plugins podem ser instalados a partir do menu Edit Preferences Install Search Packages . Também podem ser
instalados a partir do terminal (prompt de comandos) executando os comandos:
• Tab Length : 2
Com os plugins instalados Atom é capaz de marcar a sintaxe do código Haskell, completar palavras, compilar
os programas usando cabal, ou avaliar expressões no ambiente interativo diretamente dentro do editor. O menu
Haskell IDE!Open REPL carrega o arquivo no ambiente interativo integrado (REPL), onde expressões podem ser avali-
adas diretamente.
2-5
2.2.3 Outros editores de texto
Além do Atom existem vários outros editores de texto com suporte ao desenvolvimento em Haskell.
No entanto o editor de texto padrão do Windows (Bloco de Notas) não é recomendado, pois ele é muito precário
para edição de programas.
Para desabilatar o uso de tabulação no editor notepad++ marque a opção para substituir tabulações por es-
paço, acessando o menu Configurações Preferências Menu de Linguagens/Configuração de Abas Substituir por espaço antes
de editar os arquivos fontes.
O GHCi pode ser iniciado a partir de um terminal simplesmente digitando ghci. Isto é ilustrado na figura seguinte,
em um sistema Unix.
No Windows pode-se iniciar o GHCi de maneira semelhante, a partir da janela Prompt de Comandos.
2-6
No Windows o programa WinGHCi é uma alternativa para executar o GHCi sem usar um terminal. Este programa
tem uma janela própria, além de uma barra de ferramentas e uma barra de menus que podem facilitar algumas
operações no ambiente interativo. O WinGHCi pode ser iniciado a partir do menu do Windows ou do Prompt de
Comandos.
O prompt Prelude> significa que o sistema GHCi está pronto para avaliar expressões.
Uma aplicação Haskell é formada por um conjunto de módulos contendo definições de tipos, variáveis, funções,
etc. À esquerda do prompt padrão do GHCi é mostrada a lista de módulos abertos (importados) que estão disponíveis.
Um módulo é formado por definições que podem ser usadas em outros módulos. O módulo Prelude da biblioteca
padrão do Haskell contém várias definições básicas e é importado automaticamente tanto no ambiente interativo
quanto em outros módulos.
Na configuração padrão do GHCi o prompt é formado pela lista de módulos abertos seguida do símbolo >.
Expressões Haskell podem ser digitadas no prompt. Elas são compiladas e avaliadas, e o seu valor é exibido.
Por exemplo:
Prelude> 2 + 3 * 4
14
Prelude> (2 + 3) * 4
20
O GHCi também aceita comandos que permitem configurá-lo. Estes comandos começam com o caracter :
(dois-pontos). Eles não fazem parte da linguagem Haskell. São específicos do ambiente interativo.
O comando :quit pode ser usado para encerrar a sessão interativa no GHCi. A sessão pode ser encerrada
também pela inserção do caracter de fim de arquivo Control-Z no Windows e ControlD no Linux.
Normalmente a entrada para o GHCi deve ser feita em uma única linha. Assim que a tecla ENTER é digitada,
encerra-se a leitura. Para realizar entrada usando várias linhas, pode-se delimitá-la pelos comandos :{ e :}, colo-
cados cada um em sua própria linha. Por exemplo:
Prelude> :{
Prelude| 2 + 3 * 4 ^
Prelude| 5 / (8 - 7)
Prelude| :}
3074.0
As linhas entre os delimitadores :{ e :} são simplesmente unidas em uma única linha que será dada como entrada
para o GHCi.
2-7
Alternativamente pode-se configurar o GHCi para usar o modo de linhas múltimas por meio do comando :set
+m. Neste modo o GHCi detecta automaticamente quando o comando não foi finalizado e permite a digitação de
linhas adicionais. Uma linha múltipla pode ser terminada com uma linha vazia. Por exemplo:
Prelude> :set +m
Prelude> sqrt (2 +
Prelude| 3 * 4)
3.7416573867739413
Prelude> :help
Commands available from the prompt:
2-8
:print [<name> ...] show a value without forcing its computation
:sprint [<name> ...] simplified version of :print
:step single-step after stopping at a breakpoint
:step <expr> single-step into <expr>
:steplocal single-step within the current top-level binding
:stepmodule single-step restricted to the current module
:trace trace after stopping at a breakpoint
:trace <expr> evaluate <expr> with tracing on (see :history)
2.4 Bibliotecas
Os programas em Haskell são organizados em módulos. Um módulo é formado por um conjunto de defini-
ções (tipos, variáveis, funções, etc.). Para que as definições de um módulo possam ser usadas o módulo deve ser
importado. Uma biblioteca é formada por uma coleção de módulos relacionados.
A biblioteca padrão8 é formada por um conjunto de módulos disponível em tdas as implementações de Haskell.
Ela contém o módulo Prelude9 que é importado automaticamente por padrão em todos os programas em Haskell e
contém tipos e funções comumente usados.
A biblioteca padrão do GHC10 é uma versão expandida da biblioteca padrão contendo alguns módulos adicio-
8
Veja http://www.haskell.org/onlinereport/haskell2010/haskellpa2.html.
9
Veja http://www.haskell.org/ghc/docs/latest/html/libraries/base/Prelude.html.
10
Veja http://www.haskell.org/ghc/docs/latest/html/libraries/index.html.
2-9
nais.
As bibliotecas da Plataforma Haskell11 são bibliotecas extras incluídas na plataforma Haskell.
Hackage12 é uma coleção de pacotes contendo bibliotecas disponibilizados pela comunidade de desenvolve-
dores. Estes pacotes podem ser instalados separadamente.
Todas as definições de um módulo podem ser listadas no ambiente interativo usando o comando :browse.
Exemplo:
Tarefa 2.1
Use o ambiente interativo GHCi para avaliar todas as expressões usadas nos exemplos deste roteiro.
11
Veja http://www.haskell.org/platform/.
12
Veja http://hackage.haskell.org/.
2-10
3 Expressões e Definições
Resumo
Neste capítulo são apresentados alguns elementos básicos da linguagem Haskell que permitirão
a construção de expressões envolvendo constantes, variáveis e funções.
Sumário
3.1 Constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-1
3.1 Constantes
As formas mais básicas de expressões são os construtores constantes e os literais, que representam valores em
sua forma mais simples, ou seja, já estão reduzidos à sua forma canônica. Uma expressão está na forma canônica
quando ela não pode mais ser simplificada.
Os literais são expressões com sintaxe especial para escrever alguns valores. Já construtores constantes
são identificadores começando com letra maiúscula.
Veja alguns exemplos de construtores constantes e literais na tabela a seguir.
3-1
descrição exemplo
em decimal 8743
0o7464
em octal
inteiros 0O103
0x5A0FF
em hexadecimal
0xE0F2
literais numéricos
140.58
8.04e7
fracionários em decimal 0.347E+12
5.47E-12
47e22
’H’
literais caracter ’\n’
’\x65’
"bom dia"
literais string
"ouro preto\nmg"
False
construtores booleanos
True
Prelude> sqrt 25
5.0
Prelude> cos 0
1.0
Prelude> tan pi
-1.2246467991473532e-16
Prelude> exp 1
2.718281828459045
Prelude> logBase 3 81
4.0
Prelude> log 10
2.302585092994046
Prelude> mod 25 7
4
3-2
Observe que, diferentemente de várias outras linguagens de programação, os argumentos não são escritos
entre parênteses e nem separados por vírgula.
Aplicações de função também podem ser escritas em notação infixa, onde a função é escrita entre os seus
argumentos. Neste caso dizemos que as funções são operadores infixos. Exemplos:
Prelude> 2 + 3
5
Prelude> 10 / 4
2.5
Prelude> (12 - 7) * 6
30
Prelude> 5 * sqrt 36
30.0
Prelude> 6 <= 17
True
3-3
precedência associativade operador descrição
9 esquerda !! índice de lista
direita . composição de funções
8 direita ^ potenciação com expoente inteiro não negativo
^^ potenciação com expoente inteiro
** potenciação com expoente em ponto flutuante
7 esquerda * multiplicação
/ divisão fracionária
‘div‘ quociente inteiro truncado em direção a −∞
‘mod‘ módulo inteiro satisfazendo
(div x y)*y + (mod x y) == x
‘quot‘ quociente inteiro truncado em direção a 0
‘rem‘ resto inteiro satisfazendo
(quot x y)*y + (rem x y) == x
6 esquerda + adição
- subtração
5 direita : construção de lista não vazia
++ concatenção de listas
4 não associativo == igualdade
/= desigualdade
< menor que
<= menor ou igual a
> maior que
>= maior ou igual a
‘elem‘ pertinência de lista
‘notElem‘ negação de pertinência de lista
3 direita && conjunção (e lógico)
2 direita || disjunção (ou lógico)
1 esquerda >>= composição de ações sequenciais
>> composição de ações sequenciais
(ignora o resultado da primeira)
0 direita $ aplicação de função
$! aplicação de função estrita
‘seq‘ avaliação estrita
Exemplos:
3-4
512
Aplicações de função em notação prefixa tem prioridade maior do que todos os operadores. Exemplos:
Um operador pode ser associativo à esquerda, associativo à direita, ou não-associativo. Quando dois operadores
com a mesma precedência disputam um operando,
• se eles forem não associativos, a expressão é mal formada e contém um erro de sintaxe,
Exemplos:
Prelude> 15 - (4 - 6)
17
Prelude> 10 - (2 + 5)
3
Prelude> (2^3)^4
4096
O símbolo - merece atenção especial, pois ele pode tanto ser a função de subtração (operador infixo) como a
função de inversão de sinal (operador prefixo).
Prelude> 6 - 2
4
Prelude> - 5
-5
Prelude> - (5 - 9)
4
Prelude> negate (5 - 9)
4
Prelude> 4 * (-3)
-12
3-5
Prelude> 4 * -3
erro de sintaxe
A notação prefixa é usada com nomes de funções que são identificadores alfanuméricos: formados por uma
sequência de letras, dígitos decimais, sublinhado (_) e apóstrofo (’) começando com letra minúscula ou sublinhado
(e que não seja uma palavra reservada).
Já a notação infixa é usada com nomes de funções simbólicos: formados por uma sequência de símbolos
especiais (! # $ % & + . / < = > ? @ | \ ^ - ~ :) que não começa com :.
Qualquer operador pode ser usado em notação prefixa, bastando escrevê-lo entre parênteses. Exemplos:
Prelude> (+) 4 5
9
Prelude> (>=) 10 20
False
Qualquer função prefixa de dois argumentos pode ser usada em notação infixa, bastando escrevê-la entre após-
trofos invertidos (sinal de crase: ‘), com precedência padrão 9 e associativade à esquerda. Exemplos:
Prelude> 20 ‘div‘ 3
6
Prelude> 20 ‘mod‘ 3
2
Prelude> 20 ‘mod‘ 3 == 0
False
Prelude> 3 ‘logBase‘ 81
4.0
Prelude> 3 ‘logBase‘ 81 ^ 2
16.0
Prelude> 20 ‘div‘ 3 ^ 2
2
Prelude> 2 + 3 * 4
14
3-6
Prelude> it
14
Prelude> 7*(it - 4)
70
Prelude> it
70
Uma declaração let pode ser usada para definir uma variável no ambiente interativo. Por exemplo:
Prelude> idade
14
Prelude> 7*(idade - 4)
70
A posição s de um corpo em movimento retilínio uniformemente variado, em função do tempo t , é dado pela
equação
1
s = s0 + v0 t + at 2
2
onde s0 é a posição inicial do corpo, v0 é a sua velocidade inicial, e a é a sua acelaração.
Utilize o ambiente interativo GHCi para calcular a posição de uma bola em queda livre no instante
t = 8 s, considerando que a posição inicial é s0 = 100 m, a velocidade inicial é v0 = 15 m/s e a acelaração
da gravidade é a = −9.81 m/s2 .
Dicas: Use a declaração let para criar variáveis correspondentes aos dados e em seguida avalie a
expressão correspondente à função horária do movimento usando estas variáveis.
Utilize o ambiente interativo para avaliar as expressões aritméticas seguintes, considerando que x = 3 e
y = 4.
4
a) π sin x 2 − 1
3
x2 y 3
b)
(x − y) 2
s
1 35 √
c) 2 − e−4x + 3
xy
x −y y
24 + 4.53
d)
e4.4 − log10 12560
π
5π 2 7π tan ( 6 ln 8)
e) cos sin + √
6 8 7+2
3-7
3.5 Definindo variáveis e funções
Além de poder usar as funções das bibliotecas, o programador também pode definir e usar suas próprias funções.
Novas funções são definidas em arquivos texto geralmente chamdos de código fonte ou programa fonte ou ainda
script. Um programa fonte contém definições (de variáveis, funções, tipos, etc.) usadas para estruturar o código da
aplicação.
Por convenção, arquivos de programas fonte em Haskell normalmente tem a extensão .hs em seu nome. Isso
não é obrigatório, mas é útil para fins de identificação.
Variáveis e funções são definidas usando equações. No lado esquerdo de uma equação colocamos o nome da
variável ou o nome da função seguido de seus parâmetros formais. No lado direito colocamos uma expressão cujo
valor será o valor da variável ou o resultado da função quando a função for aplicada em seus argumentos.
identificadores alfanuméricos
• começam com uma letra minúscula ou sublinhado e podem conter letras, dígitos decimais, sublinhado
(_) e apóstrofo (aspa simples ’)
• exemplos:
myFun
fun1
arg_2
x’
identificadores simbólicos
• formados por uma sequência de símbolos e não podem começar com dois pontos (:)
• exemplos:
<+>
===
$*=*$
+=
Ao desenvolver um programa pode ser útil manter duas janelas abertas, uma executando um editor de texto
para editar o código, e outra para o ambiente interativo (GHCi) em execução. Na seção2.2 são apresentadas
algumas sugestões de editores de texto que poderão ser usados para escrever seus programas.
Os arquivos de programas em Haskell sempre devem ser salvos usando a codificação de caracteres UTF-8.
3-8
Tarefa 3.3: Meu primeiro script
Inicie um editor de texto, digite as seguintes definições de função, e salve o arquivo com o nome test.hs.
Deixando o editor aberto, em outra janela execute o GHCi carregando o novo arquivo fonte:
$ ghci test.hs
GHCi, version 7.10.3: http://www.haskell.org/ghc/ :? for help
[1 of 1] Compiling Main ( test.hs, interpreted )
Ok, modules loaded: Main.
*Main>
Agora, tanto Prelude.hs como test.hs são carregados, e as funções de ambos os arquivos fonte
podem ser usadas:
*Main> quadruplo 10
40
*Main> 5*(dobro 2) - 3
17
Observe que o GHCi usa o nome de módulo Main se o arquivo fonte não define um nome para o módulo.
Deixando o GHCi aberto, volte para o editor, adicione as seguintes definições ao arquivo fonte test.hs, e
salve-o.
areaCirculo r = pi * r^2
O GHCi não detecta automaticamente que o arquivo fonte foi alterado. Assim ocomando :reload deve
ser executado para que as novas definições possam ser usadas:
*Main> :reload
[1 of 1] Compiling Main ( test.hs, interpreted )
Ok, modules loaded: Main.
*Main> areaCirculo 5
78.53981633974483
3.6 Comentários
Comentários são usados para fazer anotações no programa que podem ajudar a entender o funcionamento do
mesmo. Os comentários são ignorados pelo compilador.
Um Comentário de linha é introduzido por -- e se estende até o final da linha.
3-9
3.7 Definições locais em equações
Em Haskell equações são usadas para definir variáveis e funções, como discutido anteriormente. Em mui-
tas situações é desejável poder definir valores e funções auxiliares em uma definição principal. Isto pode ser feito
escrevendo-se uma cláusula where ao final da equação. Uma cláusula where é formada pela palavra chave where
seguida das definições auxiliares.
A cláusula where faz definições que são locais à equação, ou seja, o escopo dos nomes definidos em uma
cláusula where restringe-se à menor equação contendo a cláusula where.
Por exemplo, considere a fórmula de Heron
A=
p
s(s − a)(s − b)(s − c)
a+b+c
s=
2
o semiperímetro do triângulo.
a
b
Como s aparece várias vezes na fórmula, podemos defini-lo localmente uma única vez e usá-lo quantas vezes forem
necessárias na equação.
Esta definição assume que os argumentos da função são valores válidos para os lados de um triângulo.
areaTriangulo 5 6 8
sqrt (s * (s-5) * (s-6) * (s-8))
where
s = (5 + 6 + 8)/2
9.5
sqrt (9.5 * (9.5-5) * (9.5-6) * (9.5-8))
sqrt 224.4375
14.981238266578634
Tanto funções como variáveis podem ser definidas localmente. A ordem das equações locais é irrelevante. Por
exemplo:
minhaFuncao x = 3 + f x + f a + f b
where
f x = x + 7*c
a = 3*c
b = f 2
c = 10
3-10
minhaFuncao 5
3 + f 5 + f a + f b
where f x = x + 7*c
a = 3*c
3*10
30
b = f 2
2 + 7*10
2 + 70
72
c = 10
3 + (5 + 7*10) + (30 + 7*10) + (72 + 7*10)
3 + (5 + 70) + (30 + 70) + (72 + 70)
3 + 75 + 100 + 142
320
a = 10 a = 10 a = 10
b = 20 b = 20 b = 20
c = 30 c = 30 c = 30
Se uma definição for escrita em mais de uma linha, as linhas subsequentes à primeira devem começar em uma
coluna mais à direita da coluna que começa a sequência de definições.
a = 10 + 20 + 30 + a = 10 + 20 + 30 + a = 10 + 20 + 30 +
40 + 50 + 60 + 40 + 50 + 60 + 40 + 50 + 60 +
70 + 80 70 + 80 70 + 80
b = sum [10,20,30] b = sum [10,20,30] b = sum [10,20,30]
A regra de layout evita a necessidade de uma sintaxe explícita para indicar o agrupamento de definições usando
{, } e ;.
-- agrupamento implícito
a = b + c
where
b = 1
c = 2
d = a * 2
significa
-- agrupamento explícito
a = b + c
where { b = 1 ; c = 2 }
d = a * 2
3-11
Para evitar problemas com a regra de layout, é recomendado não utilizar caracteres de tabulação para inden-
tação do código fonte, uma vez que um único caracterizar de tabulação pode ser apresentado na tela como
vários espaços. O texto do programa vai aparentar estar alinhado na tela do computador, mas na verdade
pode não estar devido ao uso do tabulador.
3.10 Exercícios
Nas tarefas seguintes, quando for solicitado para definir funções, elas devem ser definidas em um arquivo fonte
e testadas no GHCi.
Tarefa 3.5: Encontrando os erros
N = a ’div’ length xs
where
a = 10
xs = [1,2,3,4,5]
Tarefa 3.6
Tarefa 3.7
3-12
Tarefa 3.9: Energia armazenada em uma mola
A força requerida para comprimir uma mola linear é dada pela equação
F = kx
1 2
E= kx
2
onde E é a energia em J (joule).
Defina funções para calcular a compressão e a energia potencial armazenada em uma mola, dadas a
constante elástica da mola e a força usada para comprimi-la.
Sabe-se que o quilowatt de energia elétrica custa um quinto do salário mínimo. Defina uma função que
receba o valor do salário mínimo e a quantidade de quilowatts consumida por uma residência, e resulta no
valor a ser pago com desconto de 15%.
Uma versão simplificada da parte frontal de um receptor de rádio AM é apresentada na figura abaixo. Esse
receptor é composto por um circuito que contém um resistor R, um capacitor C e um indutor L conectados
em série. O circuito é conectado a uma antena externa e aterrado conforme mostra a figura.
Antena
− L −
C
V0 R VR
+ +
Terra
O circuito permite que o rádio selecione uma estação específica dentre as que transmitem na faixa
AM. Na frequência de resonância do circuito, essencialmente todo o sinal V0 da antena vai até o resistor,
que representa o resto do rádio. Em outras palavras, o rádio recebe seu sinal mais forte na frequência de
ressonância. A frequência de ressonância do circuito indutor-capacitor é dada pela equação
1
f0 = √
2π LC
onde L é a indutância em H (henry) e C é a capcitância em F (farad).
Defina uma função que receba a indutância L e a capacitância C , e resulta na frequência de ressonância
desse aparelho de rádio
Teste seu programa pelo cálculo da frequência do rádio quando L = 0, 25mH e C = 0, 10nF .
3-13
Tarefa 3.12: Área de um triângulo usando relações métricas
A área de um triângulo de lados a, b e c pode ser calculada usando relações métricas em um triângulo
qualquer.
a
h
b
90o
c α
b2 + c2 − a2
a2 = b2 + c2 − 2bc cos α =⇒ cos α =
2bc
Pela relação fundamental da trigonometria temos:
p
sin2 α + cos2 α = 1 =⇒ sin α = 1 − cos2 α
Pela definição de seno temos:
h
sin α = =⇒ h = b sin α
b
Pela definição da área de um triângulo temos:
ch
A=
2
Defina uma função para calcular a área de um triângulo de lados a, b e c usando as esquações apre-
sentadas.
Dicas: Use variáveis locais para calcular os valores cos α , sin α e h.
3-14
3.11 Soluções
Tarefa 3.1 on page 3-7: Solução
n = a ‘div‘ length xs
where
a = 10
xs = [1,2,3,4,5]
dobroDoQuadrado x = 2 * x^2
3-15
ladosTriangulo a b c =
a + b > c &&
a + c > b &&
b + c > a
compressao k f = f / k
freqRes l c =
1 / (2 * pi * sqrt (l * c))
areaTri a b c = c*h/2
where
cosAlpha = (b^2 + c^2 - a^2)/(2*b*c)
sinAlpha = sqrt (1 - cosAlpha^2)
h = b*sinAlpha
3-16
4 Tipos de Dados
Resumo
As linguagens funcionais modernas apresentam um sistema de tipos robusto que permite ao com-
pilador verificar se os operandos usados nas operações são consistentes. Com a inferência de tipos
isto pode ser feito sem o programador ter que necessariamente anotar os tipos das variáveis e funções
usadas nos programas.
Nesta aula vamos conhecer alguns tipos básicos de Haskell.
Sumário
4.1 Tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4-1
4.1 Tipos
Um tipo é uma coleção de valores relacionados. Tipos servem para classificar os valores de acordo com as
suas características.
Em Haskell nomes de tipo são sequências de letras, dígitos decimais, sublinhados e apóstrofo, começando
com uma letra maiúscula.
Por exemplo, o tipo Bool contém os dois valores lógicos False e True, comumente usados nas operações
lógicas.
4-1
tipo características exemplos de valores
Int – inteiros de precisão fixa 876
– limitado (tem um valor mínimo e um 2012
valor máximo)
– faixa de valores determinada pelo ta-
manho da palavra da plataforma
Integer – inteiros de precisão arbitrária 10
– ilimitado (qualquer número inteiro 7547387487840030454523342092382
pode ser representado desde que
haja memória suficiente)
– menos eficiente que Int
Float – aproximação de números reais em 4.56
ponto flutuante 0.201E10
– precisão simples
Double – aproximação de números reais em 78643
ponto flutuante 987.3201E-60
– precisão dupla
Rational – números racionais 3 % 4
– precisão arbitrária 8 % 2
– representados como uma razão de 5 % (-10)
dois valores do tipo Integer
– os valores podem ser construídos
usando o operador % do módulo
Data.Ratio (precedência 7 e asso-
ciatividade à esquerda)
import Data.Ratio
Bool – valores lógicos False
True
Char – enumeração cujos valores represen- ’B’
tam caracteres unicode ’!’
– estende o conjunto de caracteres ISO ’\n’ nova linha
8859-1 (latin-1), que é uma extensão ’\LF’ nova linha
do conjunto de caracteres ASCII ’\^J’ nova linha
’\10’ nova linha
’\’’ aspas simples
’\\’ barra invertida
String – sequências de caracteres "Brasil"
""
"bom\ndia"
"altura:\10\&199.4"
"primeiro/ /segundo"
Alguns literais são sobrecarregados. Isto significa que um mesmo literal pode ter mais de um tipo, dependendo
do contexto em que é usado. O tipo correto do literal é escolhido pela análise desse contexto.
Em particular:
• os literais inteiros podem ser de qualquer tipo numérico, como Int, Integer, Float, Double ou Rational,
e
• os literais fracionários podem ser de qualquer tipo numérico fraconário, como Float, Double ou Rational.
Por exemplo:
4-2
• o literal inteiro 2016 pode ser de qualquer tipo numérico (como Int, Integer, Float, Double ou Rational)
• o literal 5.61 pode ser de qualquer tipo fracionário (como Float, Double ou Rational).
Nas linguagens funcionais uma função é um valor de primeira classe e, assim como os demais valores, tem um
tipo. Este tipo é caracterizado pelos tipos dos argumentos e pelo tipo do resultado da função.
Em Haskell um tipo função é escrito usando o operador de tipo ->:
onde
• t n é o tipo do resultado
Exemplos:
Toda expressão sintaticamente correta tem o seu tipo calculado em tempo de compilação. Se não for possível
determinar o tipo de uma expressão ocorre um erro de tipo que é reportado pelo compilador.
A aplicação de uma função a um ou mais argumentos de tipo inadequado constitui um erro de tipo.
Por exemplo:
<interactive>:6:5:
Couldn’t match expected type ‘Bool’ with actual type ‘Char’
In the first argument of ‘not’, namely ’A’
In the expression: not ’A’
In an equation for ‘it’: it = not ’A’
Neste exemplo o erro ocorre porque a função not, cujo tipo é Bool -> Bool, requer um valor booleano, porém foi
aplicada ao argumento ’A’, que é um caracter.
Haskell é uma linguagem fortemente tipada, com um sistema de tipos muito avançado. Todos os possíveis
erros de tipo são encontrados em tempo de compilação (tipagem estática). Isto torna os programas mais seguros
e mais rápidos, eliminando a necessidade de verificações de tipo em tempo de execução.
4-3
4.5 Assinatura de tipo em definições
Ao fazer uma definição de variável ou função, o seu tipo pode ser anotado usando uma assinatura de tipo
imediatamente antes da equação. A anotação consiste em escrever o nome e o tipo separados pelo símbolo ::.
Por exemplo:
notaFinal :: Double
notaFinal = media2 4.5 7.2
No GHCi, o comando :type (ou de forma abreviada :t ) calcula o tipo de uma expressão, sem avaliar a expres-
são.
Exemplos:
4-4
Tarefa 4.1: Força gravitacional
A lei da gravitação universal, proposta por Newton a partir das observações de Kepler sobre os movimentos
dos corpos celestes, diz que:
Dois corpos quaisquer se atraem com uma força diretamente proporcional ao produto de suas
massas e inversamente proporcional ao quadrado da distância entre eles.
Essa lei é formalizada pela seguinte equação:
m1 m2
F=G
d2
onde:
b) Defina uma função que recebe as massas dos dois corpos e a distância entre eles, e resulta na força
de atração entre esses dois corpos. Use a variável definida em a).
c) Teste suas definições no ambiente interativo calculando a força de atração entre a terra e a lua sa-
bendo que a massa da terra é 6 × 1024 kg, a massa da lua é 1 × 1023 kg, e a distância entre eles é
4 × 105 km.
Defina uma função que recebe o salário base de um funcionário e resulta no salário líquido a receber,
sabendo-se que o funcionário tem gratificação de 10% sobre o salário base e paga imposto de 7% sobre
o salário base.
Use uma anotação de tipo para a função.
Exemplos:
4-5
4.7 Soluções
Tarefa 4.1 on page 4-5: Solução
cteGravitacaoUniversal :: Double
cteGravitacaoUniversal = 6.67e-11
4-6
5 Expressão Condicional
Resumo
Expressões condicionais permitem a escolha entre duas alternativas na obtenção do valor da ex-
pressão, com base em uma condição (expressão lógica).
Nesta aula vamos nos familiarizar com o uso de expressões condicionais.
Sumário
5.1 Expressão condicional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-1
5.2 Definição de função com expressão condicional . . . . . . . . . . . . . . . . 5-2
5.3 Equações com guardas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-3
5.4 Definições locais e guardas . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-5
5.5 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-6
5.6 Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5-9
onde condição é uma expressão booleana (chamada predicado) e exp1 (chamada consequência) e exp2 (cha-
mada alternativa) são expressões de um mesmo tipo. O valor da expressão condicional é o valor de exp1 se a
condição é verdadeira, ou o valor de exp2 se a condição é falsa.
Seguem alguns exemplos de expressões condicionais e seus valores.
A expressão condicional é uma expressão, e portanto sempre tem um valor. Assim uma expressão condicional
pode ser usada dentro de outra expressão. Veja os exemplos seguintes.
Observe nos exemplos seguintes que uma expressão condicional se extende à direita o quanto for possível.
A cláusula else de uma expressão condicional não é opcional. Omiti-la é um erro de sintaxe. Se fosse possível
omiti-la, qual seria o valor da expressão quando a condição fosse falsa? Não teria nenhum valor neste caso, o que
seria um problema. Assim uma expressão condicional sempre deve ter as duas alternativas. Por exemplo, a seguinte
expressão apresenta um erro de sintaxe, pois foi omitida a cláusula else.
5-1
Regra de inferência de tipo
test :: Bool
e1 :: a
e2 :: a
if test then e1 else e2 :: a
Observe que a consequência e a alternativa devem ser do mesmo tipo, que também é o tipo do resultado.
Exemplos no ambiente interativo:
Tarefa 5.1
Determine o valor e o tipo das expressões seguintes caso a expressão esteja correta. Se a expressão estiver
incorreta, indique qual é o problema encontrado.
a) if sqrt (abs (10 - 35) * 100) < 5 then "aceito" else "negado"
5-2
sinal :: Int -> Int
sinal n = if n < 0
then -1
else if n == 0
then 0
else 1
Defina uma função max3 que recebe três valores inteiros e resulta no maior deles. Use expressões condici-
onais aninhadas.
Faça uma anotação de tipo para a função em seu código.
Teste sua função no ambiente interativo.
Funções podem ser definidas através de equações com guardas, onde uma sequência de expressões lógicas,
chamadas guardas, é usada para escolher entre vários possíveis resultados.
Uma equação com guarda é formada por uma sequência de cláusulas escritas logo após a lista de argumen-
tos. Cada cláusula é introduzida por uma barra vertical (|) e consiste em uma condição, chamada guarda, e uma
expressão (resultado), separados por =.
Observe que:
• cada guarda deve ser uma expressão lógica, e
• os resultados devem ser todos do mesmo tipo.
Quando a função é aplicada, as guardas são verificadas na sequência em que foram escritas. A primeira guarda
verdadeira define o resultado.
Como exemplo, considere uma função para calcular o valor absoluto de um número:
vabs 89
?? 89 >= 0
?? True
89
Observe que quando o cálculo do valor de uma expressão é escrito passo a passo, indicamos o cálculo das guardas
separadamente em linhas que começam com ??.
Veja outra exemplo de aplicação da função:
5-3
vabs (75 - 2*50)
?? 75 - 2*50 >= 0
?? 75 - 100 >= 0
?? -25 >= 0
?? False
?? -25 < 0
?? True
- (-25)
25
Note que o argumento (75 - 2*50) é avaliado uma única vez, na primeira vez em que ele é necessário (para
verificar a primeira guarda). O seu valor não é recalculado quando o argumento é usado novamente na segunda
guarda ou no resultado. Esta é uma característica da avaliação lazy:
Um argumento é avaliado somente se o seu valor for necessário, e o seu valor é guardado caso ele
seja necessário novamente.
Logo um argumento nunca é avaliado mais de uma vez.
Observe que na definição de vabs o teste n < 0 pode ser substituído pela constante True, pois ele somente
será usado se o primeiro teste n >= 0 for falso, e se isto acontecer, com certeza n < 0 é verdadeiro:
vabs n | n >= 0 = n
| True = -n
vabs n | n >= 0 = n
| otherwise = -n
otherwise é uma condição que captura todas as outras situações que ainda não foram consideradas. otherwise
é definida no prelúdio simplesmente como o valor verdadeiro:
otherwise :: Bool
otherwise = True
Equações com guardas podem ser usadas para tornar definições que envolvem múltiplas condições mais fáceis
de ler, como mostra o exemplo a seguir para determinar o sinal de um número inteiro:
Como outro exemplo temos uma função para análisa o índice de massa corporal:
Uma definição pode ser feita com várias equações. Se todas as guardas de uma equação forem falsas, a próxima
equação é considerada. Se não houver uma próxima equação, ocorre um erro em tempo de execução. Por exemplo:
funcaoTeste 2 3 -1
funcaoTeste 3 2 1
funcaoTeste 2 2 ERRO
5-4
Tarefa 5.3: Menor de três valores
a) min3 2 3 4
b) min3 5 (4-3) 6
Tarefa 5.4
a
b
areaTriangulo a b c
| medidasValidas = sqrt (s * (s-a) * (s-b) * (s-c))
| otherwise = 0
where
medidasValidas = a > 0 && b > 0 && c > 0 &&
a < b + c &&
b < a + c &&
c < a + b
s = (a + b + c)/2
5-5
Veja outro exemplo de definição local em uma equação com guardas:
g x y | x <= 10 = x + a
| x > 10 = x - a
where a = (y+1)^2
Ou ainda:
5.5 Exercícios
Defina uma função chamada numRaizes que recebe os três coeficientes de uma equação do segundo grau
ax 2 + bx + c = 0
e calcula a quantidade de raízes reais distintas da equação. Assuma que a equação é não degenerada (isto
é, o coeficiente do termo de grau dois não é zero).
Use uma definição local para calcular o discriminante da equação.
∆ = b2 − 4ac
Se ∆ for positivo a equação tem duas reais reais e distintas, se for nulo, a equação tem uma raiz real, e
se for negativo, a equação não tem raízes reais.
Especifique o tipo da função.
5-6
Tarefa 5.6: Custo do retrato
O estúdio fotográfico Boa Imagem cobra de seus clientes por retratos antigos baseando-se no número de
indivíduos incluídos no retrato. As tarifas constam da tabela seguinte.
indivíduos no retrato preço base
1 R$100,00
2 R$130,00
3 R$150,00
4 R$165,00
5 R$175,00
6 R$180,00
7 ou mais R$185,00
Retratos antigos tirados aos sábados ou aos domings custam 20% a mais do que o preço base.
Defina uma função precoRetrato do tipo Integer -> String -> Doubleque recebe como argu-
mentos o número de pessoas no retrato e o dia da semana agendado, e calcula o custo do retrato.
Exemplos:
A nota final de um estudante é calculada a partir de três notas atribuídas respectivamente a um trabalho de
laboratório, a uma avaliação semestral e a um exame final. A média ponderada das três notas mencionadas
obedece aos pesos a seguir:
nota peso
trabalho de laboratório 2
avaliação semestral 3
exame final 5
O programa a seguir, que está incompleto, recebe as três notas e determina e exibe o conceito obtido pelo
aluno usando a tabela:
5-7
module Main where
main :: IO ()
main =
do hSetBuffering stdout NoBuffering
putStr "Digite a nota do trabalho de laboratório ...: "
laboratório <- readLn
putStr "Digite a nota da avaliação semestral .......: "
semestral <- readLn
putStr "Digite a nota do exame final ...............: "
final <- readLn
putStrLn ""
putStr "Conceito obtido: "
let conceitoObtido = conceito laboratório semestral final
putStrLn [conceitoObtido]
*Main> main
Digite a nota do trabalho de laboratório ...: 7.8
Digite a nota da avaliação semestral .......: 8.0
Digite a nota do exame final ...............: 4.9
Conceito obtido: C
Você deve completar a definição do função conceito. Use uma definição local para calcular a média,
e guardas para selecionar uma das cinco alternativas.
5-8
5.6 Soluções
Tarefa 5.1 on page 5-2: Solução
1) if sqrt (abs (10 - 35) * 100) < 5 then "aceito" else "negado"
"negado"
String
min3 2 3 4
?? 2 < 3 && 2 < 4
?? True && 2 < 4
?? True && True
?? True
2
min3 5 (4-3) 6
?? 5 < (4-3) && 5 < 6
?? 5 < 1 && 5 < 6
?? False && 5 < 6
?? False
?? 1 < 6
?? True
1
5-9
min3 (div 100 5) (2*6) (4+5)
?? div 100 5 < 2*6 && div 100 5 < 4+5
?? 20 < 2*6 && 20 < 4+5
?? 20 < 12 && 20 < 4+5
?? False && 20 < 4+5
?? False
?? 12 < 4+5
?? 12 < 9
?? False
?? otherwise
?? True
9
5-10
conceito :: Float -> Float -> Float -> Char
conceito notaLaboratorio notaSemestral notaFinal
| media >= 8 = ’A’
| media >= 7 = ’B’
| media >= 6 = ’C’
| media >= 5 = ’D’
| otherwise = ’E’
where
media = (2*notaLaboratorio + 3*notaSemestral + 5*notaFinal)/10
5-11
6 Programas Interativos
Resumo
Programas interativos se comunicam com o usuário recebendo dados e exibindo resultados. Nesta
aula vamos aprender como desenvolver programs funcionais que interagem com o usuário.
Sumário
6.1 Interação com o mundo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-1
6.1.1 Programas interativos . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-1
6.1.2 Linguagens puras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-2
6.1.3 O mundo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-2
6.1.4 Modificando o mundo . . . . . . . . . . . . . . . . . . . . . . . . . . 6-2
6.1.5 Ações de entrada e saída . . . . . . . . . . . . . . . . . . . . . . . . . 6-3
6.2 O tipo unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-3
6.3 Ações de saída padrão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-3
6.4 Ações de entrada padrão . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-4
6.5 Programa em Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-5
6.6 Combinando ações de entrada e saída . . . . . . . . . . . . . . . . . . . . . 6-6
6.7 Exemplos de programas interativos . . . . . . . . . . . . . . . . . . . . . . 6-7
6.8 Saída bufferizada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-9
6.9 Mais exemplos de programas interativos . . . . . . . . . . . . . . . . . . . . 6-11
6.10 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-12
6.11 Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6-18
Exemplo de programa interativo em C Programa que obtém dois caracteres digitados pelo usuário e
exibe-os em maiúsculas na tela:
6-1
#include <stdio.h>
#include <ctype.h>
int main(void)
{
char x = getchar();
char y = getchar();
printf("%c%c\n", toupper(x), toupper(y));
return 0;
}
Supondo que o usuário informe os caracteres ’A’ e ’b’, a execução do programa produzirá a seguinte interação:
Ab
AB
A aplicação de função getchar() retorna valores diferentes mesmo quando chamada com os mesmos argu-
mentos (nenhum argumento, neste caso). A primeira chamada retorna ’A’ e a segunda chamada retorna ’b’. Isto
acontece porque getchar() utiliza uma variável global representando o dispositivo de entrada padrão (stdin).
Durante a chamada da função esta variável é atualizada (efeito colateral), removendo o próximo caracter disponível
na entrada e retornando-o como resultado. Assim, quando a função getchar() é chamada novamente, o próximo
caracter disponível na entrada padrão (representada pela variável global stdin) é o segundo caracter digitado pelo
usuário.
teste = ...
where x = getchar ()
y = getchar ()
Em uma linguagem pura os valores de x e y serão iguais, uma vez que são definidos aplicando a função getchar
ao mesmo argumento (a tupla vazia).
6.1.3 O mundo
Para interagir com o usuário, precisamos de uma representação do sistema de computação onde o programa
está sendo executado: o mundo (world). O mundo é formado por todas as informações no contexto de execução
da aplicação, incluindo:
• conexões de rede
• gerador de números pseudo-aleatórios (usa uma semente que depende do sistema, como por exemplo o
horário atual)
6-2
Em uma linguagem pura não há a possibilidade de alterar uma variável. Uma função pura que interage com o
mundo tem um argumento e um resultado adicionais que representam o mundo antes e o mundo depois da interação.
Uma ação de entrada e saída (E/S) é um valor que representa uma interação com o mundo. Uma ação de E/S
pode ser executada pelo sistema computacional para interagir com o mundo e retornar um valor obtido através desta
interação.
Em Haskell IO a é o tipo das ações de entrada e saída que interagem com o mundo e retornam um valor do
tipo a. IO a é um tipo abstrato, logo sua representação não está disponível nos programas.
Haskell provê algumas ações de entrada e saída primitivas, e um mecanismo para combinar ações de entrada
e saída.
É comum nas linguagens de programação a existência de um tipo cujo conjunto de valores é vazio ou unitário.
Ou seja, não há nenhum valor, ou há exatamente um valor desse tipo.
Em Haskell o tipo que só possui um valor é escrito como () e é comumente chamado unit. O seu único valor
também é escrito como () e é chamado de tupla vazia.
O tipo unit é comumente usado para indicar o tipo de uma expressão que não possui um valor interessante,
irrelevante para o cálculo da expressão maior que a contém. Corresponde à noção do tipo void da linguagem C.
Ele ocorre com frequência nos tipos das ações de entrada e saída que, quando executadas, não têm nenhum valor
interessante para o retorno. Neste caso o mais importante é o efeito que a ação produz no mundo. Por exemplo,
uma ação para exibir uma mensagem na saída padrão produz um efeito, porém não tem nenhum valor de retorno
útil. Por este motivo o valor retornado por esta ação é a tupla vazia.
A função putChar
putChar é uma função que recebe um caracter e resulta em uma ação de E/S que, quando executada, interage
com o mundo inserindo o caracter na saída padrão e retorna a tupla vazia ().
Quando executada, a ação putChar x apenas insere x na saída padrão e não há nenhum valor interessante
para ser retornado. Como toda ação deve retornar um valor quando executada, a tupla vazia () é usada.
Exemplo: o valor da expressão
putChar ’H’
é uma ação de E/S que, quando executada, interage com o mundo inserindo o caracter ’H’ na saída padrão e retorna
e tupla vazia.
A função putStr
A função putStr recebe uma string e resulta em uma ação de E/S que, quando executada, interage com o
mundo inserindo a string na saída padrão e retorna a tupla vazia.
6-3
A função putStrLn
A função putStrLn recebe uma string e resulta em uma ação de E/S que, quando executada, interage com o
mundo inserindo a string seguida do caracter ’\n’ na saída padrão e retorna a tupla vazia.
A função print
A função print recebe um valor e resulta em uma ação de E/S que, quando executada, insere na saída padrão
o valor convertido para string, seguido de mudança de linha, e retorna a tupla vazia.
A conversão para string é feita usando a função show :: Show a => a -> String . Portanto o tipo do valor
deve ser instância da classe Show.
A ação getChar
getChar :: IO Char
A ação de E/S getChar , quando executada, interage com o mundo extraindo o próximo caracter disponível da
entrada padrão e retorna este caracter.
A ação getChar levanta uma exceção (que pode ser identificada pelo predicado isEOFError do módulo IO)
se for encontrado fim de arquivo na entrada padrão.
A ação getLine
getLine :: IO String
A ação de E/S getLine , quando executada, interage com o mundo extraindo a próxima linha disponível na
entrada padrão e retorna esta linha. A ação getLine pode falhar com uma exceção se encontrar o fim de arquivo
ao ler o primeiro caracter.
A ação getContents
getContents :: IO String
A ação de E/S getContents , quando executada, interage com o mundo extraindo todos os caracteres da
entrada padrão e retorna a string formada pelos caracteres.
A ação readLn
A ação de E/S readLn , quando executada, interage com o mundo extraindo a próxima linha disponível na
entrada padrão e retorna um valor obtido dessa string.
6-4
A conversão da string para o valor é feita usando uma função similar à função read, com a diferença de que
se a conversão falhar o programa não termina, mas uma exceção é levantada no sistema de E/S. Portanto o tipo do
valor deve ser instância da classe Read1 .
A ação readLn é sobrecarregada no seu retorno, uma vez que este retorno pode ser de qualquer tipo que permita
a conversão a partir do tipo String.
Já que Haskell é uma linguagem pura, você pode estar perguntando quando é que uma ação de entrada e saída
é executada.
Quando o sistema operacional executa o programa, a ação de E/S é executada. Executar o programa implica
em executar a ação de E/S que o constitui. Logo não é Haskell que é responsável pela interação com o mundo, mas
o sistema operacional. Desta forma a linguagem continua sendo pura.
Um programa é organizado como uma coleção de módulos. Um dos módulos deve ser chamado Main e
deve exportar a variável main , do tipo IO t , para algum tipo t. Quando o programa é executado pelo sistema
operacional, a ação main é executada, e o seu resultado (do tipo t) é descartado.
Exibir um caracter.
main :: IO ()
main = putChar ’A’
1. main recebe (automaticamente) como argumento o mundo existente antes de sua execução,
4. produzindo um novo mundo que reflete o efeito das ações de entrada e saída realizadas.
1
Classes de tipo serão estudadas detalhadamente em capítulos posteriores. Elas são usadas para indicar so-
brecarga, ou seja, a possibilidade de um mesmo nome representar vários valores diferentes de tipos diferentes no
mesmo contexto.
6-5
Tarefa 6.1: Preparando e executando um programa em Haskell
main :: IO ()
main = putChar ’A’
$ ./putchar-a
A
Sendo IO a um tipo abstrato, como poderíamos combinar duas ações em sequência? Por exemplo, como
exibir os caracteres ’A’ e ’B’ em sequência?
Haskell tem uma forma de expressão (expressão do) que permite combinar ações de entrada e saída a serem
executadas em sequência. Exemplo:
Uma expressão do permite combinar várias ações de E/S de forma sequencial. Uma expressão do é da forma
main :: IO ()
main = do { putChar ’F’ ; putChar ’i’ ; putChar ’m’ }
6-6
O código seguinte é idêntico ao anterior, porém com um layout diferente:
main :: IO ()
main = do { putChar ’F’
; putChar ’i’
; putChar ’m’
}
A expressão do pode usar a regra de layout da mesma maneira que declarações locais na cláusula where.
Assim as chaves { e } e os pontos-e-vírgula ; podem ser omitidos na sequência de ações, sendo substituídos
por uso de indentação adequada.
este caso, cada ação que compõe a expressão do deve começar na mesma coluna e, se continuar em linhas
subsequentes, deve sempre ocupar as colunas à direita da coluna onde iniciou. Uma linha que começa em uma
coluna mais à esquerda da coluna de referência encerra a expressão do.
Exemplo: exibe três caracteres na saída padrão.
main :: IO ()
main = do putChar ’F’
putChar ’i’
putChar ’m’
main :: IO Char
main = getChar
main :: IO ()
main = do caracter <- getChar
putChar caracter
6-7
module Main (main) where
main :: IO ()
main = do letra <- getChar
putChar (toLower letra)
putChar (toUpper letra)
Exemplo: saudação
main :: IO ()
main = do putStrLn "Qual é o seu nome? "
nome <- getLine
putStr nome
putStrLn ", seja bem vindo(a)!"
main :: IO ()
main = do putStrLn "Digite um número: "
s1 <- getLine
putStrLn "Digite outro número: "
s2 <- getLine
putStr "Soma dos números digitados: "
putStrLn (show (read s1 + read s2))
main :: IO ()
main = do putStrLn "Digite um número: "
n1 <- readLn
putStrLn "Digite outro número: "
n2 <- readLn
putStr "Soma dos números digitados: "
putStrLn (show (n1 + n2))
6-8
Tarefa 6.2
Escreva um programa que solicita ao usuário três números em ponto flutuante, lê os números, e calcula e
exibe o produto dos números.
Dicas: Provavelmente será necessária uma anotação de tipo para que o programa funcione com nú-
meros em ponto flutuante, pois a operação de multiplicação é definida para todos os tipos numéricos e, não
havendo informações no contexto suficientes para decidir o tipo numérico a ser usado, o tipo Integer é
escolhido. A anotação de tipo pode ser feita em qualquer subexpressão do programa.
Exemplo de execução da aplicação
Digite um número:
10
Digite outro número:
2.3
Digite outro número:
5
Produto dos números digitados: 115.0
main :: IO ()
main =
do putStr "Digite um número: " -- observe que não há uma mudança de linha
s1 <- getLine
putStr "Digite outro número: " -- observe que não há uma mudança de linha
s2 <- getLine
putStr "Soma dos números digitados: "
putStrLn (show (read s1 + read s2))
34
17
Digite um número: Digite outro número: Soma dos números digitados: 51
O tipo Handle (definido no módulo System.IO) é um tipo abstrato que representa um dispositivo de E/S inter-
namente para o Haskell.
O módulo System.IO define variáveis que representam alguns dispositivos padrões:
6-9
stdin :: Handle -- entrada padrão
stdout :: Handle -- saída padrão
stderr :: Handle -- saída de erro padrão
Para que o exemplo dado funcione corretamente é necessário esvaziar o buffer da saída padrão antes de fazer
a entrada de dados, como mostra a nova versão do programa.
main :: IO ()
main = do putStr "Digite um número: "
hFlush stdout -- esvazia o buffer de saída
s1 <- getLine
putStr "Digite outro número: "
hFlush stdout -- esvazia o buffer de saída
s2 <- getLine
putStr "Soma dos números digitados: "
putStrLn (show (read s1 + read s2))
Digite um número: 34
Digite outro número: 17
Soma dos números digitados: 51
A função hSetBuffering (definida no módulo System.IO) pode ser utilizada para configurar o modo de buf-
ferização de um dispositivo.
é uma ação que, quando executada, configura o modo de bufferização para o handler hdl.
Então podemos corrigir o problema no exemplo dado anteriormente adicionando a ação
main :: IO ()
main = do hSetBuffering stdout NoBuffering -- desabilita a bufferização
putStr "Digite um número: "
s1 <- getLine
putStr "Digite outro número: "
s2 <- getLine
putStr "Soma dos números digitados: "
putStrLn (show (read s1 + read s2))
6-10
Execução do programa onde o usuário informa os números 34 e 17:
Digite um número: 34
Digite outro número: 17
Soma dos números digitados: 51
Peso ideal
Escrever um programa em Haskell que recebe a altura e o sexo de uma pessoa e calcula e mostra o seu peso
ideal, utilizando as fórmulas constantes na tabela a seguir.
main :: IO ()
main =
do hSetBuffering stdout NoBuffering
putStr "Altura (m): "
h <- readLn
putStr "Sexo (f/m): "
s <- getLine
if s == "F" || s == "f"
then putStrLn ("Peso ideal: " ++ show (62.1 * h - 44.7))
else if s == "M" || s == "m"
then putStrLn ("Peso ideal: " ++ show (72.7 * h - 58))
else putStrLn "Sexo inválido"
Situação do aluno
Faça um programa que receba três notas de um aluno, e calcule e mostre a média aritmética das notas e a
situação do aluno, dada pela tabela a seguir.
6-11
module Main (main) where
import System.IO (stdout, hSetBuffering, BufferMode(NoBuffering))
6.10 Exercícios
Escreva um programa em Haskell que solicita ao usuário para digitar uma frase, lê a frase da entrada padrão,
e testa se a frase lida é uma palíndrome, exibindo uma mensagem apropriada.
A frase deve ser lida como a próxima linha da entrada.
Exemplo de execução da aplicação
Dicas: Para verificar se uma frase é palíndrome basta verificar se ela é igual à sua reversa. Para
reverter uma string pode-se usar a função reverse do prelúdio.
6-12
Tarefa 6.4: Conversão de temperaturas
Escreva um programa em Haskell que solicita ao usuário uma temperatura na escala Fahrenheit, lê esta
temperatura, converte-a para a escala Celsius, e exibe o resultado.
Para fazer a conversão, defina uma função celsius :: Double -> Double que recebe a tempe-
ratura na escala Fahrenheit e resulta na temperatura correspondente na escala Celsius. Use a seguinte
equação para a conversão:
5
C= × (F − 32)
9
onde F é a temperatura na escala Fahrenheit e C é a temperatura na escala Celsius.
Use a função celsius para definir a variável main no módulo Main.
A digitação da temperatura em Fahrenheit deve ser feita na mesma linha onde é exibida a mensagem
que a solicita.
Exemplo de execução da aplicação
A prefeitura de Contagem abriu uma linha de crédito para os funcionários estatutários. O valor máximo da
prestação não poderá ultrapassar 30% do salário bruto.
Fazer um programa que permita entrar com o salário bruto e o valor da prestação, e informar se o
empréstimo pode ou não ser concedido.
Exemplo de execução da aplicação
Análise de crédito
-------------------------------------------
Salário bruto: 1000
Valor da prestação: 20
O empréstimo pode ser concedido
Análise de crédito
-------------------------------------------
Salário bruto: 1000
Valor da prestação: 430.23
O empréstimo não pode ser concedido
6-13
Tarefa 6.6: Classe eleitoral
Crie um programa que leia a idade de uma pessoa e informe a sua classe eleitoral:
não eleitor abaixo de 16 anos;
eleitor obrigatório entre 18 (inclusive) e 65 anos;
Classe eleitoral
-------------------------------------------
Digite a idade da pessoa: 11
não eleitor
Classe eleitoral
-------------------------------------------
Digite a idade da pessoa: 17
eleitor facultativo
Classe eleitoral
-------------------------------------------
Digite a idade da pessoa: 20
eleitor obrigatório
Classe eleitoral
-------------------------------------------
Digite a idade da pessoa: 73
eleitor facultativo
Faça um programa que apresente o menu a seguir, permita ao usuário escolher a opção desejada, receba
os dados necessários para executar a operação, e mostre o resultado.
--------------------------------
Opções:
--------------------------------
1. Imposto
2. Novo salário
3. Classificação
--------------------------------
Digite a opção desejada:
6-14
Na opção 2 receba o salário de um funcionário, calcule e mostre o valor do novo salário, usando as
regras a seguir:
salário aumento
Acima de R$1.500,00 R$25,00
De R$750,00 (inclusive) a R$1.500,00 (inclusive) R$50,00
De R$450,00 (inclusive) a R$750,00 R$75,00
Abaixo de R$450,00 R$100,00
Na opção 3 receba o salário de um funcionário e mostre sua classificação usando a tabela a seguir:
salário classificação
Até R$750,00 (inclusive) mal remunerado
Acima de R$750,00 bem remunerado
Análise de salário
--------------------------------
Opções:
--------------------------------
1. Imposto
2. Novo salário
3. Classificação
--------------------------------
Digite a opção desejada: 1
Cálculo do imposto
Digite o salário: 700
Imposto calculado: 70.0
Análise de salário
--------------------------------
Opções:
--------------------------------
1. Imposto
2. Novo salário
3. Classificação
--------------------------------
Digite a opção desejada: 2
6-15
Exemplo de execução da aplicação
Análise de salário
--------------------------------
Opções:
--------------------------------
1. Imposto
2. Novo salário
3. Classificação
--------------------------------
Digite a opção desejada: 3
Classificação do salário
Digite o salário: 700
Classificação obtida: mal remunerado
Análise de salário
--------------------------------
Opções:
--------------------------------
1. Imposto
2. Novo salário
3. Classificação
--------------------------------
Digite a opção desejada: 4
Opção inválida!
6-16
Tarefa 6.8: Terno pitagórico
Em Matemática um terno pitagórico (ou trio pitagórico, ou ainda tripla pitagórica) é formado por três números
a, b e c tais que a2 + b2 = c2 . O nome vem do teorema de Pitágoras que afirma que se as medidas dos
lados de um triângulo retângulo são números inteiros, então elas formam um terno pitagórico.
Codifique um programa que leia três números positivos e verifique se eles formam um terno pitagórico.
Exemplo de execução da aplicação
6-17
6.11 Soluções
main :: IO ()
main = do putStrLn "Digite um número: "
n1 <- readLn :: IO Double
putStrLn "Digite outro número: "
n2 <- readLn
putStrLn "Digite outro número: "
n3 <- readLn
putStr "Produto dos números digitados: "
print (n1 * n2 * n3)
main :: IO ()
main = do putStrLn "Digite uma frase:"
frase <- getLine
if frase == reverse frase
then putStrLn "É uma palíndrome."
else putStrLn "Não é uma palíndrome."
main :: IO ()
main = do hSetBuffering stdout NoBuffering
putStr "Temperatura em Fahrenheit: "
str <- getLine
let f = read str
c = converte f
putStrLn ("Temperatura em Celsius: " ++ show c)
6-18
module Main (main) where
main =
do hSetBuffering stdout NoBuffering
putStrLn "Análise de crédito"
putStrLn "-------------------------------------------"
putStr "Salário bruto: "
salario <- readLn
putStr "Valor da prestação: "
prestação <- readLn
if prestação <= 0.3 * salario
then putStrLn "O empréstimo pode ser concedido"
else putStrLn "O empréstimo não pode ser concedido"
classeEleitoral idade
| idade < 16 = "não eleitor"
| idade >= 18 && idade < 65 = "eleitor obrigatório"
| otherwise = "eleitor facultativo"
main =
do hSetBuffering stdout NoBuffering
putStrLn "Classe eleitoral"
putStrLn "-------------------------------------------"
putStr "Digite a idade da pessoa: "
idade <- readLn
putStrLn (classeEleitoral idade)
6-19
module Main (main) where
main =
do hSetBuffering stdout NoBuffering
putStrLn "Análise de salário"
putStrLn "--------------------------------"
putStrLn "Opções:"
putStrLn "--------------------------------"
putStrLn "1. Imposto"
putStrLn "2. Novo salário"
putStrLn "3. Classificação"
putStrLn "--------------------------------"
putStr "Digite a opção desejada: "
opção <- readLn
putStrLn ""
if opção == 1
then do putStrLn "Cálculo do imposto"
putStr "Digite o salário: "
salário <- readLn
let taxa | salário < 500 = 5
| salário < 850 = 10
| otherwise = 15
imposto = taxa * salário / 100
putStrLn ("Imposto calculado: " ++ show imposto)
else if opção == 2
then do putStrLn "Cálculo do novo salário"
putStr "Digite o salário: "
salário <- readLn
let aumento | salário > 1500 = 25
| salário >= 750 = 50
| salário >= 450 = 75
| otherwise = 100
novoSalário = salário + aumento
putStrLn ("Novo salário: " ++ show novoSalário)
else if opção == 3
then do putStrLn "Classificação do salário"
putStr "Digite o salário: "
salário <- readLn
let classificação | salário <= 750 = "mal remunerado"
| otherwise = "bem remunerado"
putStrLn ("Classificação obtida: " ++ classificação)
else putStrLn "Opção inválida!"
6-20
module Main (main) where
main =
do hSetBuffering stdout NoBuffering
putStrLn "Verificação de ternos pitagóricos"
putStrLn "-----------------------------------------"
n1 <- prompt "Digite o primeiro número positivo .....: "
n2 <- prompt "Digite o segundo número positivo ......: "
n3 <- prompt "Digite o terceiro número positivo .....: "
if n1 <= 0 || n2 <= 0 || n3 <= 0
then putStrLn "Números inválidos"
else if ternoPitagórico n1 n2 n3
then putStrLn "Os números formam um terno pitagórico"
else putStrLn "Os números não formam um terno pitagórico"
6-21
7 Funções Recursivas
Resumo
Definições recursivas são comuns na programação funcional. Nesta aula vamos aprender a definir
funções recursivas.
Sumário
7.1 Recursividade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-1
7.2 Recursividade mútua . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-5
7.3 Recursividade de cauda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-5
7.4 Vantagens da recursividade . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-7
7.5 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-8
7.6 Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7-10
7.1 Recursividade
Recursividade é uma idéia inteligente que desempenha um papel central na programação funcional e na ciência
da computação em geral. Recursividade é o mecanismo de programação no qual uma definição de função ou de
outro objeto refere-se ao próprio objeto sendo definido. Assim função recursiva é uma função que é definida em
termos de si mesma. São sinônimos: recursividade, recursão, recorrência.
Recursividade é o mecanismo básico para repetições nas linguagens funcionais.
Estratégia para a definição recursiva de uma função:
1. dividir o problema em problemas menores do mesmo tipo
2. resolver os problemas menores (dividindo-os em problemas ainda menores, se necessário)
3. combinar as soluções dos problemas menores para formar a solução final
Ao dividir o problema sucessivamente em problemas menores eventualmente os casos simples são alcançados:
• não podem ser mais divididos
• suas soluções são definidas explicitamente
De modo geral, uma definição de função recursiva é dividida em duas partes:
• Há um ou mais casos base que dizem o que fazer em situações simples, onde não é necessária nenhuma
recursão.
– Nestes casos a resposta pode ser dada de imediato, sem chamar recursivamente a função sendo
definida.
– Isso garante que a recursão eventualmente pode parar.
• Há um ou mais casos recursivos que são mais gerais, e definem a função em termos de uma chamada mais
simples a si mesma.
Como exemplo de função recursiva, considere o cálculo do fatorial de um número natural. A função que calcula
o fatorial de um número natural pode ser definida recursivamente como segue:
7-1
Nesta definição:
• A segunda guarda estabelece que o fatorial de um número positivo é o produto deste número e do fatorial do
seu antecessor. Este é o caso recursivo.
Observe que no caso recursivo o subproblema fatorial (n-1) é mais simples que o problema original fatorial n
e está mais próximo do caso base fatorial 0 .
Aplicando a função fatorial:
fatorial 4
fatorial 3 * 4
(fatorial 2 * 3) * 4
((fatorial 1 * 2) * 3) * 4
(((fatorial 0 * 1) * 2) * 3) * 4
(((1 * 1) * 2) * 3) * 4
((1 * 2) * 3) * 4
(2 * 3) * 4
6 * 4
24
Em muitas implementações de linguagens de programação uma chamada de função usa um espaço de memória
(chamado de quadro, frame ou registro de ativação) em uma área da memória (chamada pilha ou stack) onde
são armazenadas informações importantes, tais como:
• argumentos da função
• variáveis locais
• variáveis temporárias
Normalmente para cada nova chamada de função que é realizada, um novo quadro é alocado na pilha. Quando a
função retorna, o quadro é desalocado.
De maneira bastante simplificada a sequência de alocação de quadros na pilha de execução na chamada da
função fatorial 6 do exemplo anterior é indicada nas figuras seguintes.
7-2
Tarefa 7.1: Aplicando fatorial
Digite a função fatorial em um arquivo fonte Haskell e carregue-o no ambiente interativo de Haskell.
a) Mostre que fatorial 7 = 5040 usando uma sequência de passos de simplificação.
b) Determine o valor da expressão fatorial 7 usando o ambiente interativo.
c) Determine o valor da expressão fatorial 1000 usando o ambiente interativo. Se você tiver uma
calculadora científica, verifique o resultado na calculadora.
d) Qual é o valor esperado para a expressão div (fatorial 1000) (fatorial 999)? Determine
o valor desta expressão usando o ambiente interativo.
Vejamos outro exemplo. A função que calcula a potência de dois (isto é, a base é dois) para números naturais
pode ser definida recursivamente como segue:
Nesta definição:
• A primeira cláusula estabelece que 20 = 1. Este é o caso base.
• A segunda cláusula estabelece que 2n = 2 × 2n−1 , sendo n > 0. Este é o caso recursivo.
Observe que no caso recursivo o subproblema potDois (n-1) é mais simples que o problema original potDois n
e está mais próximo do caso base potDois 0 .
Aplicando a função potência de dois:
potDois 4
2 * potDois 3
2 * (2 * potDois 2)
2 * (2 * (2 * potDois 1))
2 * (2 * (2 * (2 * potDois 0)))
2 * (2 * (2 * (2 * 1)))
2 * (2 * (2 * 2))
2 * (2 * 4)
2 * 8
16
Tarefa 7.2
Vejamos mais um exemplo de definição recursiva. A multiplicação de inteiros está disponível na biblioteca como
uma operação primitiva por questões de eficiência. Porém ela pode ser definida usando adicão e recursividade em
um de seus argumentos:
Nesta definição:
7-3
• A primeira cláusula estabelece que quando o multiplicador é zero, o produto também é zero. Este é o caso
base.
• A terceira cláusula estabelece que m × n = −(m × (−n)) , sendo n < 0. Este é outro caso recursivo.
% mul 7 (-3)
negate (mul 7 (negate (-3)))
negate (mul 7 3)
negate (7 + mul 7 2)
negate (7 + (7 + mul 7 1))
negate (7 + (7 + (7 + mul 7 0)))
negate (7 + (7 + (7 + 0)))
negate (7 + (7 + 7))
negate (7 + 14)
negate 21
-21
A definição recursiva da multiplicação formalisa a idéia de que a multiplicação pode ser reduzida a adições repetidas.
Tarefa 7.3
Vamos analisar outro exemplo de função recursiva: a sequência de Fibonacci. Na seqüência de Fibonacci
0, 1, 1, 2, 3, 5, 8, 13, . . .
os dois primeiros elementos são 0 e 1, e cada elemento subseqüente é dado pela soma dos dois elementos que o
precedem na seqüência. A função a seguir calcula o n-ésimo número de Fibonnaci, para n ≥ 0:
Nesta definição:
Neste caso temos recursão múltipla, pois a função sendo definida é usada mais de uma vez em sua própria defini-
ção.
Aplicando a função de fibonacci:
fib 5
fib 3 + fib 4
(fib 1 + fib 2) + (fib 2 + fib 3)
(1 + (fib 0 + fib 1)) + ((fib 0 + fib 1) + (fib 1 + fib 2))
(1 + (0 + 1)) + ((0 + 1) + (1 + (fib 0 + fib 1)))
(1 + 1) + (1 + (1 + (0 + 1)))
2 + (1 + (1 + 1))
2 + (1 + 2)
2 + 3
5
Tarefa 7.4
7-4
7.2 Recursividade mútua
Recursividade mútua ocorre quando duas ou mais funções são definidas em termos uma da outra. Para
exemplificar vamos definir as funções par e ímpar usando recursividade mútua As funções da biblioteca even (par)
e odd (ímpar), que determinam se um número é par ou ímpar, respectivamente, geralmente são definidas usando o
resto da divisão por dois:
par n = mod n 2 == 0
par (-5)
par 5
impar 4
par 3
impar 2
par 1
impar 0
False
No caso recursivo, o resultado da chamada recursiva fatorial (n-1) é multiplicado por n para produzir o
resultado final.
A função recursiva a seguir também não apresenta recursividade de cauda:
7-5
par :: Integer -> Bool
par n
| n == 0 = True
| n > 0 = not (par (n-1))
No caso recursivo, a função not é aplicada ao resultado da chamada recursiva par (n-1) para produzir o
resultado final.
Já a função recursiva pdois’ a seguir apresenta recursividade de cauda:
No caso recursivo, o resultado da chamada recursiva pdois’ (n-1) (2*y) é o resultado final.
Tarefa 7.5
Tarefa 7.6
Uma chamada de cauda acontece quando uma função chama outra função como sua última ação, não tendo
mais nada a fazer além desta última ação. O resultado final da função é dado pelo resultado da chamada de cauda.
Em tais situações o programa não precisa voltar para a função que chama quando a função chamada termina.
Portanto, após a chamada de cauda, o programa não precisa manter qualquer informação sobre a função chamadora
na pilha.
Algumas implementações de linguagem tiram proveito desse fato e na verdade não utilizam qualquer espaço ex-
tra de pilha quando fazem uma chamada de cauda, pois o espaço na pilha da própria função chamadora é reutilizado,
já que ele não será mais necessário para a função chamadora. Esta técnica é chamada de eliminação da cauda,
otimização de chamada de cauda ou ainda otimização de chamada recursiva. A otimização de chamada de
cauda permite que funções com recursividade de cauda recorram indefinidamente sem estourar a pilha.
Considere novamente as funções pdois e pdois’.
A figura a seguir ilustra a situação da pilha de execução na avaliação da expressão pdois 3, sem otimização
de chamada de cauda.
7-6
pdois 3 pdois 3 pdois 3 pdois 3 pdois 3
n=3 n=3 n=3 n=3 n=3
pdois’ n 1 pdois’ n 1 pdois’ n 1 pdois’ n 1 pdois’ n 1
pdois’ 3 1 pdois’ 3 1 pdois’ 3 1 pdois’ 3 1
x=3 x=3 x=3 x=3
y=1 y=1 y=5 y=5
pdois’ (x-1) (2*y) pdois’ (x-1) (2*y) pdois’ (x-1) (2*y) pdois’ (x-1) (2*y)
pdois’ 2 2 pdois’ 2 2 pdois’ 2 2
x=2 x=2 x=2
y=2 y=2 y=2
pdois’ (x-1) (2*y) pdois’ (x-1) (2*y) pdois’ (x-1) (2*y)
pdois’ 1 4 pdois’ 1 4
x=1 x=1
y=4 y=4
pdois’ (x-1) (2*y) pdois’ (x-1) (2*y)
pdois’ 0 8
x=0
y=8
y 8
pdois 3 pdois 3 pdois 3 pdois 3
n=3 n=3 n=3 n=3
pdois’ n 1 pdois’ n 1 pdois’ n 1 pdois’ n 1 8
pdois’ 3 1 pdois’ 3 1 pdois’ 3 1
x=3 x=3 x=3
y=5 y=5 y=5
pdois’ (x-1) (2*y) pdois’ (x-1) (2*y) pdois’ (x-1) (2*y)
pdois’ 2 2 pdois’ 2 2 8
x=2 x=2
y=2 y=2
pdois’ (x-1) (2*y) pdois’ (x-1) (2*y)
pdois’ 1 4 8
x=1
y=4
pdois’ (x-1) (2*y)
8
Já com otimização de chamada de cauda, as chamadas recursivas reaproveitam o quadro da função chamadora,
não havendo necessidade de crescimento da pilha.
Estruturas de repetição
Muitas linguagens funcionais não possuem estruturas de repetição e usam funções recursivas para fazer repe-
tições. Nestes casos a otimização de chamada de cauda é fundamental para uma boa eficiência dos programas.
Em particular a linguagem Haskell não possui estruturas de repetição.
7-7
• propriedades de funções definidas usando recursão podem ser provadas usando indução, uma técnica ma-
temática simples, mas poderosa.
7.5 Exercícios
O fatorial duplo de um número natural n é o produto de todos os números de 1 (ou 2) até n, contados de 2
em 2. Por exemplo, o fatorial duplo de 8 é 8 × 6 × 4 × 2 = 384, e o fatorial duplo de 7 é 7 × 5 × 3 × 1 = 105.
Defina uma função para calcular o fatorial duplo usando recursividade.
Defina uma função recursiva que recebe dois números naturais m e n, e retorna o produto de todos os
números no intervalo [m, n]:
m × (m + 1) × · · · × (n − 1) × n
Usando a função definida no exercício 7.8, escreva uma definição não recursiva para calcular o fatorial de
um número natural.
Defina uma função recursiva para calcular a soma de dois números naturais, sem usar os operadores + e -.
Utilize as funções succ e pred da biblioteca, que calculam respectivamente o sucessor e o antecessor de
um valor.
Defina uma função recursiva para calcular a potência de um número, considerando que o expoente é um
número natural. Utilize o método das multiplicações sucessivas:
xn = | {z. . . × }x
x×x×
n vezes
A raiz quadrada inteira de um número inteiro positivo n é o maior número inteiro cujo quadrado é menor ou
igual a n. Por exemplo, a raiz quadrada inteira de 15 é 3, e a raiz quadrada inteira de 16 é 4.
Defina uma função recursiva para calcular a raiz quadrada inteira.
Defina duas funções recursivas que calculam o quociente e o resto da divisão inteira de dois números naturais
usando subtrações sucessivas.
Defina uma função recursiva para calcular o máximo divisor comum de dois números inteiros não negativos
a e b, usando o algoritmo de Euclides:
a se b = 0,
mdc (a, b) = mdc (b, a mod b) se b > 0,
mdc (a, −b) se b < 0
Nota: o prelúdio já tem a função gcd que calcula o máximo divisor comum de dois números inteiros.
7-8
Tarefa 7.15: Fatorial
fat n = fat’ n 1
where
fat’ n x
| n == 0 = x
| n > 0 = fat’ (n-1) (n*x)
b) Compare o cálculo de fat 6 com o cálculo de fatorial 6 apresentado anteriormente. Qual versão
da função fatorial é mais eficiente: fatorial ou fat? Explique.
Defina uma função com recursividade de cauda para calcular o n-ésimo (n ≥ 0) número de Fibonacci.
7-9
7.6 Soluções
Tarefa 7.1 on page 7-3: Solução
a) fatorial 7
fatorial 6 * 7
(fatorial 5 * 6) * 7
((fatorial 4 * 5) * 6) * 7
(((fatorial 3 * 4) * 5) * 6) * 7
((((fatorial 2 * 3) * 4) * 5) * 6) * 7
(((((fatorial 1 * 2) * 3) * 4) * 5) * 6) * 7
((((((fatorial 0 * 1) * 2) * 3) * 4) * 5) * 6) * 7
((((((1 * 1) * 2) * 3) * 4) * 5) * 6) * 7
(((((1 * 2) * 3) * 4) * 5) * 6) * 7
((((2 * 3) * 4) * 5) * 6) * 7
(((6 * 4) * 5) * 6) * 7
((24 * 5) * 6) * 7
120 * 6 * 7
720 * 7
5040
b) *Main> fatorial 7
5040
7-10
277196742822248757586765752344220207573630569498825087968928162753848
863396909959826280956121450994871701244516461260379029309120889086942
028510640182154399457156805941872748998094254742173582401063677404595
741785160829230135358081840096996372524230560855903700624271243416909
004153690105933983835777939410970027753472000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000
000000000000000
podDois’ (-5)
2 * podDois’ (-6)
2 * 2 * podDois’ (-7)
2 * 2 * 2 * podDois’ (-8)
...
A chamada recursiva da função calcula a potência de dois do antecessor do argumento original. Porém como
o argumento é negativo, o seu antecessor está mais distante de zero, o caso base. Logo a chamada recursiva não
converge para o caso base, e o cálculo nunca termina.
Este problema pode ser corrigido acrescentando uma outra alternativa na definição da fun;ão para considerar o
caso de argumentos negativos.
Tarefa 7.3 on page 7-4: Solução
mul 5 6
5 + mul 5 5
5 + (5 + mul 5 4)
5 + (5 + (5 + mul 5 3))
5 + (5 + (5 + (5 + mul 5 2)))
5 + (5 + (5 + (5 + (5 + mul 5 1))))
5 + (5 + (5 + (5 + (5 + (5 + mul 5 0)))))
5 + (5 + (5 + (5 + (5 + (5 + (5 + 0))))))
30
fib 6
fib 4 + fib 5
(fib 2 + fib 3) + (fib 3 + fib 4)
((fib 0 + fib 1) + (fib 1 + fib 2)) + ((fib 1 + fib 2) + (fib 2 + fib 3))
((0 + 1) + (1 + (fib 0 + fib 1))) + ((1 + (fib 0 + fib 1)) + ((fib 0 + fib 1) + (fib 1 + fib 2)))
((0 + 1) + (1 + (0 + 1))) + ((1 + (0 + 1)) + ((0 + 1) + (1 + (fib 0 + fib 1))))
((0 + 1) + (1 + (0 + 1))) + ((1 + (0 + 1)) + ((0 + 1) + (1 + (0 + 1))))
8
7-11
Tarefa 7.5 on page 7-6: Solução
pdois 5
pdois’ 5 1
pdois’ 4 2
pdois’ 3 4
pdois’ 2 8
pdois’ 1 16
pdois’ 0 32
32
par’ n y
| n == 0 = y
| otherwise = par’ (n-1) (not y)
fatorialDuplo n
| n == 0 = 1
| n == 1 = 1
| n > 1 = n * fatorialDuplo (n - 2)
produtoIntervalo m n
| m > n = 1
| otherwise = m * produtoIntervalo (m+1) n
fatorial n = produtoIntervalo 1 n
soma m n
| n == 0 = m
| otherwise = soma (succ m) (pred n)
potencia x n
| n == 0 = 1
| otherwise = x * potencia x (n-1)
7-12
Tarefa 7.12 on page 7-8: Solução
raizInteira n = raizInteira’ n n
where
raizInteira’ n i
| i^2 > n = raizInteira’ n (i-1)
| otherwise = i
quociente p q
| p < q = 0
| otherwise = 1 + quociente (p - q) q
resto p q
| p < q = p
| otherwise = resto (p - q) q
mdc a b
| b == 0 = a
| b > 0 = mdc b (mod a b)
| b < 0 = mdc a (negate b)
a) Cálculo:
fat 6
fat’ 6 1
fat’ 5 6
fat’ 4 30
fat’ 3 120
fat’ 2 360
fat’ 1 720
fat’ 0 720
720
b) fat é mais eficiente, pois as operações de multiplicação não ficam pendentes aguardando o retorno das
chamadas recursivas.
fibonacci n = fibonacci’ n 0 1
where
fibonacci’ n a b
| n == 0 = a
| n == 1 = b
| n >= 2 = fibonacci’ (n-1) b (a+b)
7-13
8 Ações de E/S Recursivas
Resumo
Nesta aula vamos aprender a definir ações de entrada e saída recursivas, que nos permitirá definir
ações repetitivas através de recursividade.
Sumário
8.1 A função return . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8-1
8.2 Exemplo: exibir uma sequência . . . . . . . . . . . . . . . . . . . . . . . . . 8-1
8.3 Exemplo: somar uma sequência . . . . . . . . . . . . . . . . . . . . . . . . 8-1
8.4 Problemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8-3
8.5 Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8-6
Às vezes é necessário escrever uma ação de E/S que não faz nenhuma interação com o mundo e retorna um
valor previamante especificado. Isto é possível usando a função return.
return :: a -> IO a
A função return recebe um valor e resulta em uma ação de E/S que não interage com o mundo e retorna o
valor.
A função return é muito utilizada nos casos bases de definições recursivas.
main :: IO ()
main = mostraLista [0,2..30]
8-1
-- solução sem recursividade de cauda
8-2
-- solução que separa a leitura do processamento
-- com recursividade de cauda
8.4 Problemas
Faça um programa que leia um número natural n, e então leia outros n números e calcule e exiba a soma
destes números.
Exemplo de execução da aplicação
Quantidade de números: 4
Digite um número: 10
Digite um número: -5
Digite um número: 1
Digite um número: 20
Soma dos números digitados: 26
Quantidade de números: -6
Soma dos números digitados: 0
8-3
Tarefa 8.2: Média aritmética de uma sequência de números
Faça um programa que leia uma seqüência de números não negativos e determine a média aritmética destes
números. A entrada dos números deve ser encerrada com um número inválido (negativo).
Exemplo de execução da aplicação
m(t) = m0 e−kt
onde, t é o tempo (em segundos), m0 é a++ massa inicial (em gramas) e k é a constante 5 × 10−2 .
Faça uma aplicação que, dada a massa inicial desse elemento, calcule a perda de massa durante um
minuto, exibindo as massas resultantes em intervalos de 10 segundos.
Um funcionário de uma empresa recebe aumento salarial anualmente. O primeiro aumento é de 1,5% sobre
seu salário inicial. Os aumentos subsequentes sempre correspondem ao dobro do percentual de aumento
do ano anterior. Faça uma aplicação onde o usuário deve informar o salário inicial do funcionário, o ano de
contratação e o ano atual, e calcula e exibe o seu salário atual.
8-4
Tarefa 8.6: Fechamento de notas de uma disciplina
Faça uma aplicação para fechamento das notas de uma disciplina. Cada aluno recebe uma nota para cada
uma das três atividades desenvolvidas. O usuário deverá informar a quantidade de alunos na turma, e em
seguida as notas de cada aluno. Calcule e exiba:
• a média aritmética das três notas de cada aluno,
• a situação do aluno, dada pela tabela seguinte
• a média da turma
Faça um programa para corrigir provas de múltipla escolha que foram aplicadas em uma turma de alunos. O
usuário deverá informar:
• o gabarito (as respostas corretas de cada questão) da prova
• a matrícula e as respostas de cada aluno da turma
As notas devem ser normalizadas na faixa de zero a dez. Assim para calcular a nota obtida em uma prova,
divida a soma dos pontos obtidos (um ponto para cada resposta correta) pelo número de questões na prova,
e multiplique o resulado por dez.
Calcule e mostre:
1. a matrícula e a nota de cada aluno
2. a taxa (em porcentagem) de aprovação, sabendo-se que a nota mínima para aprovação é sete.
Dicas
• Faça uma ação de E/S para obter os dados do gabarito (uma string onde caracter é a resposta correta
de uma questão).
• Faça uma ação de E/S para obter os dados das provas dos alunos (uma lista de pares, onde o primeiro
componente do par é a matrícula do aluno (um número inteiro), e o segundo componente do par são
as respostas do aluno (uma string)).
• Faça uma função que recebe o gabarito e a lista das provas dos alunos e resulta na lista dos resultados,
formada por pares contendo a matrícula e a nota do aluno.
• Faça uma função que recebe a lista dos resultados e resulta na porcentagem de aprovação.
• Use estas funções para montar a aplicação.
8-5
8.5 Soluções
8-6
module Main (main) where
8-7
9 Tuplas, Listas, e Polimorfismo Paramétrico
Resumo
Nesta aula vamos conhecer os tipos tuplas e listas, que são tipos polimórficos pré-definidos em
Haskell. Vamos aprender também sobre funções polimórficas.
Sumário
9.1 Tuplas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-1
9.2 Listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-2
9.3 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-4
9.4 Valores opcionais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-4
9.5 Polimorfismo paramétrico . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-5
9.5.1 Operação sobre vários tipos de dados . . . . . . . . . . . . . . . . . . 9-5
9.5.2 Variáveis de tipo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-5
9.5.3 Valor polimórfico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-5
9.5.4 Instanciação de variáveis de tipo . . . . . . . . . . . . . . . . . . . . . 9-6
9.6 Funções polimórficas predefinidas . . . . . . . . . . . . . . . . . . . . . . . 9-6
9.7 Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9-8
9.1 Tuplas
Tupla é uma estrutura de dados formada por uma sequência de valores possivelmente de tipos diferentes.
Os componentes de uma tupla são identificados pela posição em que ocorrem na tupla.
Em Haskell uma expressão tupla é formada por uma sequência de expressões separadas por vírgula e delimi-
tada por parênteses:
( exp1 , . . . , expn )
onde n >= 0 e n , 1, e exp1 , . . ., expn são expressões cujos valores são os componentes da tupla.
O tipo de uma tupla é o produto cartesiano dos tipos dos seus componentes. Sintaticamente um tipo tupla é
formado por uma sequência de tipos separados por vírgula e delimitada por parênteses:
( t1 , . . . , tn )
onde n >= 0 e n , 1, e t1 , . . ., tn são os tipos dos respectivos componentes da tupla. Observe que o tamanho de
uma tupla (quantidade de componentes) é codificado no seu tipo.
() é a tupla vazia, do tipo () .
Não existe tupla de um único componente.
A tabela a seguir mostra alguns exemplos de tuplas:
tupla tipo
(’A’,’t’) (Char,Char)
(’A’,’t’,’o’) (Char,Char,Char)
(’A’,True) (Char,Bool)
("Joel",’M’,True,"COM") (String,Char,Bool,String)
(True,("Ana",’f’),43) Num a => (Bool,(String,Char),a)
() ()
("nao eh tupla") String
9-1
Vejamos algumas operações com tuplas definidas no prelúdio:
9.2 Listas
Lista é uma estrutura de dados formada por uma sequência de valores (elementos) do mesmo tipo. Os ele-
mentos de uma lista são identificados pela posição em que ocorrem na lista.
Em Haskell uma expressão lista é formada por uma sequência de expressões separadas por vírgula e delimitada
por colchetes:
[ exp1 , . . . , expn ]
onde n >= 0, e exp1 , . . ., expn são expressões cujos valores são os elementos da lista.
Um tipo lista é formado pelo tipo dos seus elementos delimitado por colchetes:
[ t ]
onde t é o tipo dos elementos da lista. Observe que o tamanho de uma lista (quantidade de elementos) não é
codificado no seu tipo.
A tabela a seguir mostra alguns exemplos de listas:
lista tipo
[’O’,’B’,’A’] [Char]
[’B’,’A’,’N’,’A’,’N’,’A’] [Char]
[False,True,True] [Bool]
[ [False,True], [], [True,False,True] ] [[Bool]]
[1,8,6,10.48,-5] Fractional a => [a]
Estruturalmente uma lista pode ser de duas formas:
• lista vazia
Por exemplo, a lista formada pela sequência dos valores 1, 8, e 6 pode ser escrita como
lista
[1, 8, 6]
1 : [8, 6]
1 : (8 : [6])
1 : (8 : (6 : []))
1 : 8 : 6 : []
9-2
Observe que os parênteses neste exemplo são desnecessários, já que o operador : associa-se à direita.
Os exemplos anteriores podem ser reescritos com estes construtores de lista:
Prelude> null []
True
Prelude> head []
*** Exception: Prelude.head: empty list
• tail: seleciona a cauda da lista, ou seja, a lista formada por todos os elementos exceto o primeiro:
Prelude> tail []
*** Exception: Prelude.tail: empty list
Prelude> length []
0
• (!!): seleciona o i -ésimo elemento de uma lista (0 ≤ i < n, onde n é o comprimento da lista):
Prelude> [1,2,3,4,5] !! 2
3
Prelude> [1,2,3,4,5] !! 0
1
Prelude> [1,2,3,4,5] !! 10
*** Exception: Prelude.(!!): index too large
9-3
Prelude> [1,2,3,4,5] !! (-4)
*** Exception: Prelude.(!!): negative index
• zip: junta duas listas em uma única lista formada pelos pares dos elementos correspondentes:
9.3 Strings
Em Haskell strings são listas de caracteres. O tipo String é um sinônimo para o tipo [Char] .
A tabela a seguir mostra alguns exemplos de strings:
9-4
• valor opcional não vazio
– existe um valor
– é representado pelo construtor Just, que precisa de um argumento para indicar o valor que está na
estrutura de dados.
Não importa qual é o tipo dos elementos da lista. Qual deve ser o tipo de head?
a é uma variável de tipo e pode ser substituída por qualquer tipo. O tipo de head estabelece que head recebe uma
lista com elementos de um tipo qualquer, e retorna um valor deste mesmo tipo.
Em Haskell variáveis de tipo devem começar com uma letra minúscula, e são geralmente denominadas a, b, c,
etc.
para qualquer tipo a, head recebe uma lista de valores do tipo a e retorna um valor do tipo a.
Já o tipo da função length, que recebe uma lista e resulta no tamanho da lista, é dado por:
para qualquer tipo a, length recebe uma lista de valores do tipo a e retorna um inteiro.
A função fst é do tipo:
para quaisquer tipos a e b, fst recebe um par de valores do tipo (a,b) e retorna um valor do tipo a.
9-5
9.5.4 Instanciação de variáveis de tipo
As variaveis de tipo podem ser instanciadas para diferentes tipos em diferentes circunstâncias.
Por exemplo, a função length
Muitas das funções definidas no prelúdio são polimórficas. Algumas delas são mencionadas a seguir:
[] :: [a]
Verifique se as seguintes expressões são válidas e determine o seu tipo em caso afirmativo.
a) [’a’,’b’,’c’]
b) (’a’,’b’,’c’)
c) [(False,’0’),(True,’1’)]
d) ([False,True],[’0’,’1’])
e) [tail,init,reverse]
f) [[]]
g) [[10,20,30],[],[5,6],[24]]
h) (10e-2,20e-2,30e-3)
i) [(2,3),(4,5.6),(6,4.55)]
j) (["bom","dia","brasil"],sum,drop 7 "Velho mundo")
k) [sum,length]
9-6
Tarefa 9.2: Tipo de funções
Determine o tipo de cada uma das funções definidas a seguir, e explique o que elas calculam.
a) second xs = head (tail xs)
b) const x y = x
e) flip f x y = f y x
f) pair x y = (x,y)
g) palindrome xs = reverse xs == xs
h) twice f x = f (f x)
i) mostra (nome,idade) = "Nome: " ++ nome ++ ", idade: " ++ show idade
Defina uma função chamada ultimo que seleciona o último elemento de uma lista não vazia, usando as
funções do prelúdio.
Observação: já existe a função last no prelúdio com este propósito.
Defina uma função chamada primeiros que seleciona todos os elementos de uma lista não vazia, exceto
o último., usando as funções do prelúdio.
Observação: já existe a função init no prelúdio com este propósito.
Usando funções da biblioteca, defina a função metade :: [a] -> ([a],[a]) que divide uma lista em
duas metades. Por exemplo:
> metade [1,2,3,4,5,6]
([1,2,3],[4,5,6])
ax 2 + bx + c
9-7
9.7 Soluções
Tarefa 9.1 on page 9-6: Solução
a) [’a’,’b’,’c’]
[Char]
b) (’a’,’b’,’c’)
(Char,Char,Char)
c) [(False,’0’),(True,’1’)]
[(Bool,Char)]
d) ([False,True],[’0’,’1’])
([Bool],[Char])
e) [tail,init,reverse]
[[a] -> [a]]
f) [[]]
[[a]]
g) [[10,20,30],[],[5,6],[24]]
Num a => [[a]]
h) (10e-2,20e-2,30e-3)
(Fractional a, Fractional b, Fractional c) => (a,b,c)
i) [(2,3),(4,5.6),(6,4.55)]
(Num a, Fractional b) => [(a,b)]
j) (["bom","dia","brasil"],sum,drop 7 "Velho mundo")
Num a => ([String],[a]->a,String)
k) [sum,length]
[Int->Int]
Tarefa 9.2 on page 9-7: Solução
b) const x y = x
const :: a -> b -> a
Recebe dois valores e resulta no segundo valor, ignorando o primeiro.
d) apply f x = f x
apply :: (a -> b) -> a -> b
Recebe uma função e um valor e aplica a função no valor.
e) flip f x y = f y x
flip :: (a -> b -> c) -> b -> a -> c
Recebe uma função e dois valores e aplica a função nos dois valores invertidos.
f) pair x y = (x,y)
pair :: a -> b -> (a,b)
Recebe dois valores e resulta em um par formado pelos dois valores.
g) palindrome xs = reverse xs == xs
palindrome :: Eq a => [a] -> Bool
Recebe uma lista e verifica se ela é uma palíndrome, isto é, se a lista é igual à própria lista invertida.
9-8
h) twice f x = f (f x)
twice :: (a -> a) -> a -> a
Recebe uma função e um valor, e aplica sucessivamente a função no resultado da aplicação da função no
valor.
i) mostra (nome,idade) = "Nome: " ++ nome ++ ", idade: " ++ show idade
mostra :: Show a => (String,a) => String
Recebe um par formado por uma string e um valor e resulta na string contendo o primeiro e o segundo
elementos inseridos em uma mensagem.
9-9
10 Casamento de Padrão
Resumo
Linguagens funcionais modernas usam casamento de padrão em várias situações, como por exem-
plo na seleção de componentes de estruturas de dados, na escolha de alternativas em expressões de
seleção múltipla, e na aplicação de funções.
Nesta aula vamos aprender as principais formas de padrão e como funciona a operação casamento
de padrão. Vamos ainda conhecer algumas construções de Haskell que usam casamento de padrão,
como definições de valores e funções e expressões de seleção múltipla.
Sumário
10.1 Casamento de padrão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-1
10.1.1 Casamento de padrão . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-1
10.1.2 Padrão constante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-2
10.1.3 Padrão variável . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-2
10.1.4 Padrão curinga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-2
10.1.5 Padrão tupla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-3
10.1.6 Padrões lista . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-3
10.1.7 Padrão lista na notação especial . . . . . . . . . . . . . . . . . . . . . 10-4
10.2 Definição de função usando padrões . . . . . . . . . . . . . . . . . . . . . . 10-5
10.2.1 Definindo funções com casamento de padrão . . . . . . . . . . . . . . 10-5
10.3 Casamento de padrão em definições . . . . . . . . . . . . . . . . . . . . . . 10-9
10.4 Problema: validação de números de cartão de crédito . . . . . . . . . . . . 10-10
10.5 Problema: torres de Hanoi . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-12
10.6 Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10-14
10-1
Por exemplo o padrão ("ana", peso, _) especifica valores que são triplas onde o primeiro componente
é a string "ana", o segundo componente é chamado de peso, e o terceiro componente é irrelevante. O valor
("ana", 58.7, ’F’) casa com este padrão e em consequência a variável peso é associada ao valor 58.7. Já o
valor ("pedro", 75.3, ’M’) não casa com esse padrão.
Em um casamento de padrão, o padrão e a expressão devem ser do mesmo tipo.
Existem várias formas de padrão. Na seqüência algumas delas são apresentadas.
O padrão constante é simplesmente uma constante. O casamento sucede se e somente se o padrão for
idêntico ao valor. Nenhuma associação de variável é produzida.
Veja os exemplos seguintes, onde X indica sucesso e × indica falha:
O padrão variável é simplesmente um identificador de variável de valor (e como tal deve começar com letra
minúscula). O casamento sucede sempre. A variável é associada ao valor.
Exemplos:
O padrão curinga é escrito como um sublinhado ( _ ). O casamento sucede sempre. Nenhuma associação de
variável é produzida. _ é também chamado de variável anônima, pois, assim como a variável, casa com qualquer
valor, porém não nomeia o valor.
Exemplos:
10-2
padrão valor casamento
_ 10 X
_ 28 X
_ ’P’ X
_ () X
_ (18,3,2012) X
_ "Ana Maria" X
_ [5.6,7.1,9.0] X
( padrão1 , . . ., padrãon )
O casamento sucede se e somente se cada um dos padrões casar com o componente correspondente do valor. Se
as aridades (tamanhos) do padrão tupla e do valor tupla forem diferentes, então ocorre um erro de tipo.
Exemplos de padrão tupla:
[]
– é um padrão constante
– o casamento sucede se e somente se o valor for a lista vazia
pad 1 : pad 2
10-3
Exemplos de padrões lista:
• O padrão [] casa somente com a lista vazia.
• O padrão x:xs casa com qualquer lista não vazia, associando as variáveis x e xs com a cabeça e com a
cauda da lista, respectivamente.
• O padrão x:y:_ casa com qualquer lista que tenha pelo menos dois elementos, associando as variáveis x
e y ao primeiro e segundo elementos da lista, respectivamente.
• O padrão x:_:z:[] casa com qualquer lista que tenha exatamente três elementos, associando as variáveis
x e z ao primeiro e ao terceiro elementos da lista, respectivamente.
• O padrão 0:a:_ casa com qualquer lista de números que tenha pelo menos dois elementos, sendo o pri-
meiro igual a zero, associando a variável a ao segundo elemento da lista.
• O padrão (m,_):_ casa com qualquer lista não vazia de pares, associando a variável m ao primeiro com-
ponente do primeiro elemento da lista.
[ padrão 1 , . . . , padrão n ]
10-4
padrão 1 : ... : padrão n : []
cujo casamento sucede somente se o valor for uma lista com exatamente n elementos.
Exemplos: o padrão [1,alfa] casa com qualquer lista de dois números que começa com 1, associando a
variável alfa ao segundo elemento da lista.
10-5
(&&) :: Bool -> Bool -> Bool
True && b = b
False && _ = False
Esta definição está incorreta, pois não é possível usar uma variável mais de uma vez nos padrões (princípio da
linearidade).
Outros exemplos:
fst (1+2,1-2) 3
snd (div 5 0,even 9) False
Dê três definições diferentes para o operador lógico ou (||), utilizando casamento de padrão.
Redefina a seguinte versão do operador lógico e (&&) usando expressões condicionais ao invés de casamento
de padrão:
Redefina a seguinte versão do operador lógico e (&&) usando expressões condicionais ao invés de casamento
de padrão:
True && b = b
False && _ = False
Comente sobre o diferente número de expressões condicionais necessárias em comparação com o exer-
cício 10.2.
10-6
Tarefa 10.4: Distância entre dois pontos
Defina uma função que recebe dois pontos no espaço e retorna a distância entre eles. Considere que
um ponto no espaço é representado por uma tripla de números que são as coordenadas do ponto. Use
casamento de padrão.
Tarefa 10.5
Estude a seguinte definição e apresente uma definição alternativa mais simples desta função, usando pa-
drões.
Tarefa 10.6
Defina uma função usando casamento de padrão que retorna o sucessor do primeiro elemento de uma lista,
se houver, e zero caso contrário.
Tarefa 10.7
Usando casamento de padrão, defina uma função que, dada uma lista de números, retorna
• a soma dos dois primeiros elementos se a lista tiver pelo menos dois elementos,
Tarefa 10.8
Resolva os exercícios 10.6 e 10.7 usando funções da biblioteca ao invés de casamento de padrão.
A seguir é mostrada uma aplicação (incompleta) para fechamento das notas de uma disciplina. Cada aluno
recebe três notas nas atividades desenvolvidas. O usuário deverá informar a quantidade de alunos na turma,
e em seguida as notas de cada aluno. A aplicação calcula e exibe a média da turma.
Complete a aplicação definindo as funções somaNotas e mediaTurma.
A função somaNotas recebe uma lista com as três notas de cada aluno, e resulta na soma das médias
das três notas de todos os alunos. Esta função deverá ser recursiva:
• caso base: lista vazia; neste caso a soma é zero (elemento neutro da adicão)
• caso recursivo: lista não vazia; neste caso a soma é obtida somando a média das notas do primeiro
elemento da lista com a soma do resto da lista
A função mediaTurma recebe uma lista com as três notas de cada aluno, e resulta na média de todas as
notas. Esta função deve usar a função somaNotas e a função length. Como o resultado de length é do
tipo Int, será necessário convertê-lo para o tipo Float para calcular a média. Use a função fromIntegral
do prelúdio para converter um número de um tipo integral para qualquer tipo numérico que for apropriado no
contexto.
10-7
module Main where
main :: IO ()
main = do hSetBuffering stdout NoBuffering
putStrLn "Fechamento de notas"
putStrLn "====================================="
putStr "Quantidade de alunos: "
qtdeAlunos <- readLn
notas <- leNotas qtdeAlunos 1
let media = mediaTurma notas
putStrLn ("Média da turma: " ++ show media)
10-8
Exemplo de execução da aplicação
*Main> main
Fechamento de notas
=====================================
Quantidade de alunos: 5
aluno 1
nota 1: 10
nota 2: 10
nota 3: 10
aluno 2
nota 1: 4
nota 2: 6
nota 3: 8
aluno 3
nota 1: 6
nota 2: 7
nota 3: 6
aluno 4
nota 1: 9
nota 2: 3
nota 3: 6
aluno 5
nota 1: 0
nota 2: 7
nota 3: 5
Média da turma: 6.466667
são definidas duas variáveis, prefixo e sufixo, correspondentes ao primeiro e segundo componentes do par
resultante da função splitAt, que divide uma lista em duas partes, em uma dada posição.
Definições locais com where não são compartilhadas entre diferentes equações de uma definição principal. Por
exemplo:
10-9
Esta definição de função está incorreta. Para corrigi-la, transforme as definições locais de saudacaoLegal e
saudacaoInfeliz em definições globais.
Nesta atividade você vai implementar o algoritmo de validação para cartões de crédito. A validação segue as
etapas seguintes:
• Dobre o valor de cada segundo dígito começando pela direita. Ou seja, o último dígito não é alterado, o
penúltimo dígito é dobrado, o antepenúltimo não é alterado, e assim por diante. Por exemplo, [1,3,8,6]
torna-se [2,3,16,6].
• Adicione os dígitos dos valores dobrados e não dobrados a partir do número original. Por exemplo, [2,3,16,6]
torna-se 2 + 3 + 1 + 6 + 6 = 18.
• Calcule o resto da divisão desta soma por 10. No exemplo acima, o resto é 8.
• O número é válido se e somente se o resultado for igual a 0.
Tarefa 10.10
Precisamos primeiramente encontrar os dígitos de um número natural para processarmos o número do cartão
de crédito.
Defina as funções
toDigits deve converter um número natural na lista dos dígitos que formam este número. Para números
negativos, toDigits deve resultar na lista vazia.
toDigitsRev deve fazer o mesmo, mas com os dígitos em ordem invertida.
Exemplos:
Para obter os dígitos decimais que formam um número natural você deverá considerar os casos:
• Se o número for menor que 10, a lista dos dígitos é uma lista unitária contendo o próprio número.
• Caso contrário, divida o número por 10. O resto desta divisão é o dígito menos significativo. Os
demais dígitos são obtidos de maneira similar usando o quociente desta divisão. Por exemplo, se o
número é 538, então o dígito menos significativo é 8 (o resto da divisão de 538 por 10), e os demais
dígitos são obtidos a partir de 53 (o quociente da divisão de 538 por 10).
10-10
Tarefa 10.11
Uma vez obtidos os dígitos na ordem correta, precisamos dobrar um dígito não e outro sim, contando de trás
para frente (ou seja, da direita para a esquerda, ou ainda, do dígito menos significativo para o dígito mais
significativo).
Defina a função
para este propósito. Lembre-se de que doubleEveryOther deve dobrar os números da lista de dois em
dois, começando pelo penúltimo de trás para frente.
Exemplos:
Dica: Defina uma função auxiliar que dobra os elementos de uma lista de dois em dois, do começo para o
fim. Isto é, o primeiro elemento não é dobrado, o segundo é, o terceiro não é, o quarto é, e assim por diante.
Depois use esta função para definir doubleEveryOther. Neste caso será necessário inverter a lista antes
e depois de chamar a função auxiliar.
Tarefa 10.12
O resultado de doubleEveryOther pode conter números de um dígito ou de dois dígitos. Defina a função
sumDigits [16,7,12,5] 1 + 6 + 7 + 1 + 2 + 5 22
Dicas:
• Observe que são os dígitos dos números que devem ser somados, e não os próprios números.
• Faça uma definição recursiva que soma os elementos da lista. Divida cada elemento da lista por dez
e considere tanto o quociente quanto o resto ao efetuar a soma.
Tarefa 10.13
Defina a função
que indica se um número inteiro pode ser um número válido de cartão de crédito. Sua definição usará todas
as funções definidas nos ítens anteriores.
Exemplos:
10-11
Figura 10.1: Torres de Hanoi.
Torres de Hanoi (figura 10.1) é um quebra-cabeça clássico com uma solução que pode ser descrita de forma
recursiva. Vários discos de tamanhos diferentes são empilhados em três cavilhas. O objetivo é obter, a partir de uma
configuração inicial com todos os discos empilhados sobre a primeira cavilha, uma configuração que termina com
todos os discos empilhados sobre a última cavilha, como mostrado na figura 10.2.
As únicas regras são:
Por exemplo, como o primeiro passo tudo o que você pode fazer é mover o disco menor que se encontra no
topo do pino, para outro pino diferente, uma vez que apenas um disco pode ser movido de cada vez. A partir desta
situação, é ilegal mover para a configuração mostrada nas figura 10.4, porque você não tem permissão para colocar
o disco verde em cima do disco azul, que é menor.
Para mover n discos (empilhados em ordem crescente de tamanho) de um pino a para um pino b usando um
pino c como armazenamento temporário:
10-12
Figura 10.4: Uma configuração ilegal.
hanoi :: Integer -> Pino -> Pino -> Pino -> [Movimento]
Dados o número de discos e os nomes dos pinos para os três pinos, hanoi deve resultar na lista dos movi-
mentos que devem ser realizados para mover a pilha de discos do primeiro pino para o segundo pino, usando
o terceiro pino como armazenamento temporário.
Note que uma declaração de tipo usando a palavra chave type, como em type Pino = String, define
um sinônimo de tipo. Neste caso Pino é declarado como um sinônimo para String, e os dois nomes de
tipo Pino e String podem agora ser usado um no lugar do outro. Nomes descritivos para tipos dados desta
maneira podem ser usados para dar nomes mais curtos para tipos complicados, ou para simplesmente ajudar
na documentação, como foi feito aqui.
Exemplo:
10-13
10.6 Soluções
Tarefa 10.1 on page 10-6: Solução
e a b = a == if a
then if b
then True
else False
else False
e a b = a == if a
then b
else False
opp (1,(m,n)) = m + n
opp (2,(m,n)) = m - n
opp _ = 0
sucPrimLista [] = 0
sucPrimLista (x:_) = succ x
10-14
Tarefa 10.7 on page 10-7: Solução
flista (x:y:_) = x + y
flista [x] = x
flista _ = 0
main :: IO ()
main = do hSetBuffering stdout NoBuffering
putStrLn "Fechamento de notas"
putStrLn "====================================="
putStr "Quantidade de alunos: "
qtdeAlunos <- readLn
notas <- leNotas qtdeAlunos 1
let media = mediaTurma notas
putStrLn ("Média da turma: " ++ show media)
10-15
toDigitsRev :: Integer -> [Integer]
toDigitsRev n | n < 0 = []
| n < 10 = [ n ]
| otherwise = mod n 10 : toDigitsRev (div n 10)
hanoi :: Integer -> Pino -> Pino -> Pino -> [Movimento]
hanoi 0 _ _ _ = []
hanoi n a b c = hanoi (n-1) a c b ++ [(a,b)] ++ hanoi (n-1) c b a
10-16
11 Sobrecarga
Resumo
Haskell incorpora uma forma de polimorfismo que é a sobrecarga de funções, variáveis e literais.
Um mesmo identificador de função pode ser usado para designar funções computacionalmente dis-
tintas. A esta característa também se chama polimorfismo ad hoc.
Sumário
11.1 Sobrecarga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-1
11.2 Algumas classes de tipo pré-definidas . . . . . . . . . . . . . . . . . . . . . 11-2
11.2.1 Eq . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-2
11.2.2 Ord . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-3
11.2.3 Enum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-3
11.2.4 Bounded . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-3
11.2.5 Show . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-3
11.2.6 Read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-4
11.2.7 Num . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-4
11.2.8 Real . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-4
11.2.9 Integral . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-5
11.2.10 Fractional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-5
11.2.11 Floating . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-5
11.2.12 RealFrac . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-6
11.2.13 RealFloat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-6
11.3 Sobrecarga de literais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-7
11.4 Conversão entre tipos numéricos . . . . . . . . . . . . . . . . . . . . . . . . 11-7
11.5 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-8
11.6 Inferência de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-9
11.7 Dicas e Sugestões . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-9
11.8 Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11-11
11.1 Sobrecarga
Alungs tipos possuem operações semelhantes, porém com implementações separadas para cada tipo. Por
exemplo, a comparação de igualdade pode ser feita entre dois números inteiros, ou dois números racionais, ou dois
caracteres, ou dois valores lógicos, entre outros. Para cada tipo dos argumentos deve haver uma implementação
da operação. Para se ter o benefício de uma interface uniforme pode ser desejável que estas operações que são
semelhantes entre vários tipos tenham o mesmo nome.
Um mesmo nome de variável ou um mesmo nome de função pode estar associado a mais de um valor em um
mesmo escopo de um programa, caracterizando a sobrecarga, também chamada de polimorfismo ad hoc. Por
exemplo o módulo Prelude apresenta algumas sobrecargas, como:
11-1
• o identificador pi é sobrecarregado e denota variáveis dos tipos numéricos com representação em ponto
flutuante cujo valor é uma aproximação de π ,
• o identificador abs é sobrecarregada e denota funções que calculam o valor absoluto, cujo argumento pode
ser de qualquer tipo numérico, e cujo resultado é do mesmo tipo que o argumento,
• o operador (/) é sobrecarregado e denota funções de divisão fracionária com dois argumentos de qualquer
tipo numérico fracionário, e resultado do mesmo tipo dos argumentos.
Para expressar a sobrecarga, Haskell usa classes de tipo. Uma classe de tipo é uma coleção de tipos (chamados
de instâncias da classe) para os quais é definido um conjunto de funções (aqui chamadas de métodos) que podem
ter diferentes implementações, de acordo com o tipo considerado.
Uma classe especifica uma interface indicando o nome e a assinatura de tipo de cada função. Cada tipo que é
instância (faz parte) da classe define (implementa) as funções especificadas pela classe.
Por exemplo:
• A classe Num é formada por todos os tipos numéricos e sobrecarrega algumas operações aritméticas básicas,
como adição. Os tipos Int e Double são instâncias da classe Num. Logo existe uma definição da adição
para o tipo Int e outra para o tipo Double, usando algoritmos diferentes.
• A classe Eq é formada por todos os tipos cujos valores podem ser verificados se são iguais ou diferentes, e
sobrecarrega os operadores (==) e (/=). Logo para cada instância desta classe existe uma definição destes
operadores. Todos os tipos básicos apresentados anteriormente são instâncias de Eq. Nenhum tipo função
é instância de Eq, pois de forma geral não é possível comparar duas funções.
Em uma expressão de tipo usamos variáveis de tipo para denotar um tipo qualquer desconhecido, e um contexto
para restringi-las aos tipos que são instâncias de classes específicas.
Por exemplo:
• o tipo da função abs é Num a => a -> a , ou seja, abs é uma função que recebe um argumento de um
tipo a e resulta em um valor do mesmo tipo a, sendo a qualquer tipo que seja instância da classe Num
• o tipo do operador (*) é Num a => a -> a -> a , ou seja, (*) é uma função que recebe dois argumentos
de um mesmo tipo a e resulta em um valor deste mesmo tipo a, sendo a qualquer tipo que seja instância da
classe Num
Quando uma função sobrecarregada é usada, a escolha da implementação adequada baseia-se nos tipos dos
argumentos e do resultado da função no contexto em que ela é usada. Semelhantemente quando uma variável
sobrecarregada é usada, a escolha da implementação é feita de acordo com o contexto. Se o contexto não oferecer
informação suficiente pode ser necessário fazer anotações de tipo.
Classes de tipos podem ser parecidas com as classes das linguagens orientadas a objetos, mas elas são real-
mente muito diferentes. Elas são mais parecidas com interfaces (como na linguagem Java, por exemplo).
Pode existir uma hierarquia de classes. Se uma classe A possuir uma superclasse B , os tipos que são instâncias
de A também devem ser instâncias de B . Dizemos também neste caso que A é uma subclasse de B .
11.2.1 Eq
Valores podem ser comparados quanto à igualdade e desigualdade.
Algumas instâncias
Bool, Char, String, Int, Integer, Float, Double, Rational
Alguns métodos
11-2
11.2.2 Ord
Valores podem ser ordenados sequencialmente.
Superclasses
Eq
Algumas instâncias
Bool, Char, String, Int, Integer, Float, Double, Rational
Alguns métodos
11.2.3 Enum
Valares podem ser enumerados.
Algumas instâncias
Bool, Char, Int, Integer, Float, Double, Rational
Alguns métodos
11.2.4 Bounded
Algumas instâncias
Bool, Char, Int
Alguns métodos
11.2.5 Show
Valores podem ser convertidos para string.
11-3
Algumas instâncias
Bool, Char, String, Int, Integer, Float, Double, Rational
Alguns métodos
11.2.6 Read
Valores podem ser obtidos a partir de strings.
Algumas instâncias
Bool, Char, String, Int, Integer, Float, Double, Rational
Alguns métodos
11.2.7 Num
Valores numéricos.
Algumas instâncias
Int, Integer, Float, Double, Rational
Alguns métodos
11.2.8 Real
Números reais.
Superclasses
Eq, Num
Algumas instâncias
Int, Integer, Float, Double, Rational
11-4
Alguns métodos
11.2.9 Integral
Números inteiros.
Superclasses
Real, Enum
Algumas instâncias
Int, Integer
Alguns métodos
11.2.10 Fractional
Números fracionários.
Superclasses
Num
Algumas instâncias
Float, Double, Rational
Alguns métodos
11.2.11 Floating
Tipos cujos valores são representados como números em ponto flutuante, com as funções trancendentais tradi-
cionais.
Superclasses
Fractional
Algumas instâncias
Float, Double
11-5
Alguns métodos
11.2.12 RealFrac
Tipos que implementam Real e Fractional.
Superclasses
Real, Fractional
Algumas instâncias
Float, Double, Rational
Alguns métodos
11.2.13 RealFloat
Tipos que implementam RealFrac e Floating, com funções para manipulação de números em ponto flutuante
padronizados pela IEEE.
Superclasses
RealFrac, Floating
Algumas instâncias
Float, Double
11-6
Alguns métodos
Literais inteiros
• Podem ser de qualquer tipo numérico (como Int, Integer, Float, Double e Rational).
Exemplos:
Devido ao sistema de tipo rígido de Haskell, não podemos converter entre os tipos numéricos arbitrariamente. Às
vezes pode não ser imediatamente claro como converter de um tipo numérico para outro. A tabela 11.1 lista funções
que podem ser utilizadas para converter entre os tipos mais comuns.
11-7
11.5 Exercícios
Tarefa 11.1
ax 2 + bx + c = 0
possui raízes reais. Para tanto é necessário que o discriminante ∆ = b2 − 4ac seja não negativo.
Determine o tipo mais geral da função e use-o em uma anotação de tipo na sua definição.
Determine o valor e o tipo das expressões seguintes caso a expressão esteja correta. Se a expressão estiver
incorreta, indique qual é o problema encontrado.
1) 58 /= 58
2) abs == negate
3) False < True
4) "elefante" > "elegante"
5) min ’b’ ’h’
6) max "amaral" "ana"
7) show True
8) show 2014
9) show ’A’
10) show "adeus"
11) show max
12) read "123"
13) read "123" :: Int
14) mod (read "123") 100
15) read "’@’" :: Char
16) read "@" :: Char
17) read "\"marcos\"" :: String
18) read "marcos" :: String
19) succ ’M’
20) fromEnum ’A’
21) toEnum 65 :: Char
22) toEnum 0
23) not (toEnum 0)
24) maxBound :: Int
25) signum (-13)
26) fromInteger 99 :: Double
27) fromInteger 99 :: Rational
28) fromInteger 99
29) toRational (-4.5)
30) fromIntegral 17 :: Double
31) sin (pi/2)
32) floor (3*pi/2)
11-8
11.6 Inferência de tipos
Toda expressão bem formada tem um tipo mais geral, que pode ser calculado automaticamente em tempo de
compilação usando um processo chamado inferência de tipos.
A capacidade de inferir tipos automaticamente facilita a programação, deixando o programador livre para omitir
anotações de tipo ao mesmo tempo que permite a verificação de tipos pelo compilador.
A inferência de tipo é feita usando as regras de tipagem de cada forma de expressão.
Literais inteiros
Literais fracionários
Literais caracteres
Literais strings
Construtores constantes
Aplicação de função
f :: a1 → . . . → an → b
x 1 :: a1
..
.
x n :: an
f x 1 . . . x n :: b
• Dentro de um script, é uma boa prática indicar o tipo de cada nova função definida.
• Ao indicar os tipos de funções polimórficas que usam números, igualdade, ou ordenações (ou outras restri-
ções), tome o cuidado de incluir as restrições de classe necessárias.
11-9
Tarefa 11.3: Área do círculo
Defina uma função que recebe a medida do raio r de um círculo e resulta na área A do círculo, dada por:
A = πr 2
Defina uma função que recebe a altura dos degraus de uma escada e a altura que o usuário deseja alcançar
subindo a escada, e resulta na quantidade mínima de degraus que ele deverá subir para atingir seu objetivo,
sem se preocupar com a altura do usuário.
Faça uma anotação do tipo mais geral da função.
Tarefa 11.5
11-10
11.8 Soluções
Tarefa 11.1 on page 11-8: Solução
1) 58 /= 58
False
Bool
2) abs == negate
erro de tipo: (==) não está definido para funções
3) False < True
True
Bool
4) "elefante" > "elegante"
False
Bool
5) min ’z’ ’h’
’h’
Char
6) max "amaral" "ana"
"ana"
String
7) show True
"True"
String
8) show 2014
"2014"
String
9) show ’A’
"’A’"
String
10) show "adeus"
"\"adeus\""
String
11) show max
erro de tipo: show não está definida para funções
12) read "123"
erro de tipo: ambiguidade
13) read "123" :: Int
123
Int
14) mod (read "123") 100
23
(Integral a, Read a) => a
15) read "’@’" :: Char
’@’
Char
11-11
16) read "@" :: Char
erro em tempo de execução: sintaxe
11-12
Tarefa 11.4 on page 11-10: Solução
11-13
12 Expressão de Seleção Múltipla
Resumo
Linguagens funcionais modernas usam casamento de padrão para selecionar componentes de
estruturas de dados e também para selecionar alternativas em expressões case e em aplicações de
funções definidas com várias equações.
Nesta aula vamos aprender as principais formas de padrão. Vamos também aprender como fun-
ciona o casamento de padrão. Vamos ainda conhecer algumas construções de Haskell que usam
casamento de padrão, como a expressão case e definições usando padrões.
Sumário
12.1 Expressão case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12-1
12.2 Forma e regras de tipo da expressão case . . . . . . . . . . . . . . . . . . . 12-1
12.3 Regra de layout para a expressão case . . . . . . . . . . . . . . . . . . . . . 12-2
12.4 Avaliação de expressões case . . . . . . . . . . . . . . . . . . . . . . . . . . 12-2
12.5 Exemplos de expressões case . . . . . . . . . . . . . . . . . . . . . . . . . . 12-3
12.6 Expressão case com guardas . . . . . . . . . . . . . . . . . . . . . . . . . . 12-5
12.7 Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12-7
• uma expressão de controle, cujo valor é usado para escolher uma das alternativas
– uma expressão, usada para dar o resultado caso a alternativa seja escolhida
Exemplo:
12-1
case exp of
{ padrão 1 -> res1 ;
..
.
padrão n -> res n
}
onde:
• a expressão de controle é exp
• os resultados alternativos são dados pelas expressões res1 , . . . , res n , selecionados pelos respectivos pa-
drões padrão1 , . . . , padrãon
Regras de tipo:
• a expressão de controle exp e os padrões padrão1 , . . . , padrãon devem ser todos de um mesmo tipo
• os resultados res1 , . . . , resn devem ser todos do mesmo tipo, o que determina o tipo da expressão case (ou
seja, o tipo do resultado final)
é traduzido para
12-2
12.5 Exemplos de expressões case
A expressão
case 3 - 2 + 1 of
0 -> "zero"
1 -> "um"
2 -> "dois"
3 -> "tres"
resulta em "dois", pois o valor da expressão 3-2+1 é 2, que casa com o terceiro padrão 2, selecionando "dois"
como resultado.
A expressão
case 23 > 10 of
True -> "beleza!"
False -> "oops!"
resulta em "beleza!", pois o valor da expressão 23 > 10 é True, que casa com o primeiro padrão True, selecio-
nando "beleza!" como resultado.
A expressão
resulta em 20.0, pois o valor da expressão toUpper (head "masculino") é ’M’, que casa com o segundo padrão
’M’, selecionando 20.0 como resultado.
A expressão
resulta em um erro em tempo de execução, pois o valor da expressão head "masculino" não casa com nenhum
dos padrões.
A expressão
está incorreta, pois os resultados "mulher" e 20.0 não são do mesmo tipo.
A expressão
está incorreta, pois a expressão head "Masculino" e os padrões True e False não são do mesmo tipo.
A expressão
12-3
case toUpper (head "masculino") of
’F’ -> 10.0
’M’ -> 20.0
está incorreta, uma vez que não segue a regra de layout (os padrões não estão na mesma coluna).
A expressão
case 3 - 2 + 1 of
x -> 11 * x
resulta em 22, pois o valor da expressão 3 - 2 + 1 é 2, que casa com o primeiro padrão x, associando a variável
x com o valor 2, e selecionando 11 * x como resultado
A expressão
resulta em 6000, pois o valor da expressão mod 256 10 é 6, que casa com o segundo padrão n, associando a
variável n com o valor 6, e selecionando n * 1000 como resultado
A expressão
resulta em 0, pois 7 é o primeiro padrão que casa com o valor da expressão mod 257 10.
Já a expressão
resulta em 7000, pois n é o primeiro padrão que casa com o valor da expressão mod 257 10.
A expressão
case 46 - 2*20 of
0 -> "zero"
1 -> "um"
2 -> "dois"
3 -> "tres"
4 -> "quatro"
_ -> "maior que quatro"
resulta em "maior que quatro", pois _ é o primeiro padrão que casa com o valor da expressão 46 - 2*20.
A expressão
case (3+2,3-2) of
(0,0) -> 10
(_,1) -> 20
(x,2) -> x^2
(x,y) -> x*y - 1
resulta em 20, pois (_,1) é o primeiro padrão que casa com o valor da expressão (3+2,3-2).
A expressão
resulta em "vazia", pois o valor da expressão tail [10] casa com o padrão para lista vazia [].
12-4
A expressão
case [10,20,30,40] of
[] -> "lista vazia"
x:xs -> "cabeca: " ++ show x ++ " cauda: " ++ show xs
resulta em "cabeca: 10 cauda: [20,30,40]", pois a lista [10,20,30,40] casa com o padrão para lista não
vazia x:xs, associando x com 10 e xs com [20,30,40].
A expressão
case [10..20] of
x:y:z:_ -> x + y + z
_ -> 0
resulta em 33, pois a lista [10..20] casa com o padrão x:y:z:_, associando x com 10, y com 11 e z com 12.
A expressão
case [10,20] of
x:y:z:_ -> x + y + z
_ -> 0
resulta em 0, pois a lista [10,20] não casa com o primeiro padrão x:y:z:_, mas casa com o segundo _. Observe
que o primeiro padrão casa somente com listas que tenham pelo menos três elementos.
A expressão
case [10,20,30] of
[x1,_,x3] -> x1 + x3
_ -> 0
resulta em 40, pois a lista [10,20,30] casa com o primeiro padrão [x1,_,x3]. Observe que este padrão casa
somente com listas que tenham exatamente três elementos.
case [100,20,3] of
a:b:xs | a > b -> b:a:xs
| a == b -> a:xs
xs -> xs
12-5
resulta em [20,100,3], pois a lista [100,20,3] casa com o primeiro padrão a:b:xs e o primeiro elemento é maior
do que o segundo.
Tarefa 12.1: Seleção de um prefixo de uma lista
Defina a função prefixo :: Int -> [a] -> [a] que recebe um número inteiro n e uma lista l e resulta
na lista dos n primeiros elementos de l .
Exemplos:
Sua definição deve consistir de uma única equação sem usar casamento de padrão ou guardas para obter
o resulado. Porém o corpo da função deverá usar uma expressão case, na qual deve-se usar casamento de
padrão.
12-6
12.7 Soluções
Tarefa 12.1 on page 12-6: Solução
prefixo’ 0 _ = []
prefixo’ _ [] = []
prefixo’ n (x:xs) = x : prefixo’ (n-1) xs
12-7
13 Valores Aleatórios
Resumo
A geração de valores pseudo-aleatórios em aplicações em Haskell pode ser feita através de ações
de E/S. Nesta aula vamos aprender a desenvolver aplicações que usam valores aleatórios.
Estes tópicos serão usados na implementação do jogo adivinha o número.
Sumário
13.1 Instalação do pacote random . . . . . . . . . . . . . . . . . . . . . . . . . . 13-1
13.2 Valores aleatórios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13-1
13.3 Jogo: adivinha o número . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13-2
13.4 Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13-8
$ cabal update
$ cabal install random
O primeiro comando acessa o repositório de pacotes (hackage.haskell.org) e obtém uma lista atualizada dos
pacotes disponíveis. O segundo comando instala o pacote random, acessando o repositório para fazer o download
do seu código fonte, que é em seguida compilado e instalado no sistema.
No Ubuntu basta executar o comando seguinte para instalar a biblioteca usando o gerenciador de pacotes (o
que requer privilégios de administrador):
13-1
• randomIO:
randomIO é uma ação de E/S que, quando executada, extrai o próximo valor aleatório do tipo a do gerador
global de números aleatórios (disponível no sistema de computação), e retorna este valor.
A faixa de possíveis valores normalmente é:
• randomRIO:
Esta função recebe um par de valores (inf , sup ) e resulta em uma ação de E/S que, quando executada, extrai
o próximo valor aleatório do tipo a, uniformemente distribuído no intervalo fechado [inf , sup], do gerador global
de números aleatórios (disponível no sistema de computação), e retorna este valor.
main :: IO ()
main =
do putStrLn "Lançamento de dois dados"
x <- randomRIO (1,6::Int)
y <- randomRIO (1,6)
putStrLn ("Faces obtidas: " ++ show x ++ " e " ++ show (y::Int))
$ ./lancadados
Lancamento de dois dados
Faces obtidas: 3 e 5
$ ./lancadados
Lancamento de dois dados
Faces obtidas: 4 e 1
1. O programa escolhe um número a ser adivinhado pelo jogador (usuário) selecionando um número inteiro
aleatório no intervalo de 1 a 1000.
13-2
4. Se o palpite do jogador estiver incorreto:
• o programa exibe a mensagem Muito alto ou Muito baixo convenientemente para ajudar o joga-
dor a acertar o número nas próximas jogadas.
• o jogo continua com o programa solicitando o próximo palpite e analisando a resposta do usuário.
$ ./advinha
Adivinha o número v1.0
=========================================
Digite um número entre 1 e 1000: 444
Muito grande
Tente novamente
13-3
Tarefa 13.1
Tarefa 13.2
*Main> main
Adivinha o número v1.0
=========================================
Tarefa 13.3
Defina uma função simOuNao que recebe uma string e resulta em uma ação de E/S que, quando executada:
• exibe a string na saída padrão (com o objetivo de fazer uma pergunta do tipo sim ou não ao usuário)
• lê a resposta do usuário
• verifica se a resposta é
– s ou S, retornando verdadeiro
– n ou N, retornando falso
– qualquer outra coisa, chamando simOuNao novamente para que o usuário responda correta-
mente.
Esta função deve ser usada em jogar (veja a tarefa 13.5) para verificar se o usuário deseja continuar
jogando ou não.
13-4
Tarefa 13.4
Defina uma função acertar que recebe um número a ser adivinhado e resulta em uma ação de E/S que,
quando executada:
• exibe uma mensagem solicitando um número entre 1 e 1000
– se forem iguais, exibe uma mensagem parabenizando o usuário por ter adivinhado o número
– caso contrário
∗ exibe uma mensagem informando que o número é muito pequeno ou muito grande, ade-
quadamente
∗ exibe uma mensagem solicitando ao usuário uma nova tentativa
∗ faz uma nova tentativa através de uma chamada recursiva de acertar
A função acertar deverá ser usada na definição de jogar (veja a tarefa 13.5).
13-5
Tarefa 13.5
O programa deve permitir ao usuário jogar várias vezes, o que nos leva à necessidade do uso de recursão.
Defina uma ação de E/S jogar que, quando executada
*Main> jogar
Digite um número entre 1 e 1000: 509
Muito pequeno
Tente novamente
A ação jogar deve ser usada em main para que o usuário possa joagar o jogo.
13-6
Tarefa 13.6
Modifique o programa adivinha.hs de forma que o usuário possa especificar o intervalo a ser utilizado para
adivinhar o número através de dois argumentos na linha de comando.
Tarefa 13.7
Modifique o programa adivinha.hs para que seja exibida o número de tentativas feitas pelo usuário.
13-7
13.4 Soluções
Tarefa 13.5 on page 13-6: Solução
jogar :: IO ()
jogar =
do n <- randomRIO (1, 1000)
acertar n
putStrLn ""
resposta <- simOuNao "Deseja jogar novamente? "
putStrLn ""
if resposta
then jogar
else return ()
main :: IO ()
main =
do hSetBuffering stdout NoBuffering
putStrLn "Adivinha o número v1.0"
putStrLn "========================================="
jogar
13-8
14 Expressão Lambda
Resumo
Expressões lambdas são funções anônimas que podem ser usadas como qualquer outro valor de
primeira classe. Nesta aula vamos aprender sobre expressões lambda.
Sumário
14.1 Valores de primeira classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14-2
14.1.1 Valores de primeira classe . . . . . . . . . . . . . . . . . . . . . . . . 14-2
14.1.2 Valores de primeira classe: Literais . . . . . . . . . . . . . . . . . . . 14-2
14.1.3 Valores de primeira classe: Variáveis . . . . . . . . . . . . . . . . . . 14-2
14.1.4 Valores de primeira classe: Argumentos . . . . . . . . . . . . . . . . . 14-3
14.1.5 Valores de primeira classe: Resultado . . . . . . . . . . . . . . . . . . 14-3
14.1.6 Valores de primeira classe: Componentes . . . . . . . . . . . . . . . . 14-3
14.2 Expressão lambda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14-3
14.2.1 Expressões lambda . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14-3
14.2.2 Exemplos de expressões lambda . . . . . . . . . . . . . . . . . . . . . 14-4
14.2.3 Uso de expressões lambda . . . . . . . . . . . . . . . . . . . . . . . . 14-4
14.2.4 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14-5
14.3 Aplicação parcial de funções . . . . . . . . . . . . . . . . . . . . . . . . . . 14-6
14.3.1 Aplicação parcial de funções . . . . . . . . . . . . . . . . . . . . . . . 14-6
14.3.2 Aplicação parcial de funções: exemplos . . . . . . . . . . . . . . . . . 14-6
14.4 Currying . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14-8
14.4.1 Funções curried . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14-8
14.4.2 Por que currying é útil? . . . . . . . . . . . . . . . . . . . . . . . . . . 14-8
14.4.3 Convenções sobre currying . . . . . . . . . . . . . . . . . . . . . . . . 14-9
14.5 Seções de operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14-9
14.5.1 Operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14-9
14.5.2 Seções de operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . 14-10
14.6 Utilidade de expressões lambda . . . . . . . . . . . . . . . . . . . . . . . . . 14-12
14.6.1 Por que seções são úteis? . . . . . . . . . . . . . . . . . . . . . . . . . 14-12
14.6.2 Utilidade de expressões lambda . . . . . . . . . . . . . . . . . . . . . 14-12
14.6.3 Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14-14
14.7 Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14-15
14-1
14.1 Valores de primeira classe
matricula = 456
sexo = ’M’
aluno = ("Ailton Mizuki Sato",101408,’M’,"com")
disciplinas = ["BCC222","BCC221","MTM153","PRO300"]
livroTexto = ("Programming in Haskell","G. Hutton",2007)
Esta equação define a variável triplo, associando-a a um valor que é uma função.
Haskell permite escrever esta definição de forma mais sucinta:
triplo x = 3 * x
14-2
14.1.4 Valores de primeira classe: Argumentos
• Valores de vários tipos podem ser argumentos de funções:
sqrt 2.45
not True
length [1,6,4,5]
take 5 [1,8,6,10,23,0,0,100]
A função triplo é aplicada a cada elemento da lista [1,2,3], resultando na lista [3,6,9]
("Ana",’F’,18)
["BCC222","BCC221","MTM153","PRO300"]
[("Ailton",101408),("Lidiane",102408)]
14-3
• Expressão lambda é uma função anônima (sem nome), formada por uma seqüência de padrões represen-
tando os argumentos da função, e um corpo que especifica como o resultado pode ser calculado usando os
argumentos:
• O termo lambda provém do cálculo lambda (teoria de funções na qual as linguagens funcionais se baseiam),
introduzido por Alonzo Church nos anos 1930 como parte de uma investigação sobre os fundamentos da
Matemática.
• No cálculo lambda expressões lambdas são introduzidas usando a letra grega λ . Em Haskell usa-se o caracter
\, que se assemalha-se um pouco com λ .
\x -> x + x
\x -> 2*x + 1
\a b c -> a + b + c
f = \x -> 2*x + 1
somaPar = \(x,y) -> x + y
fatorial = \n -> product [1..n]
é o mesmo que
f x = 2*x + 1
somaPar (x,y) = x + y
fatorial n = product [1..n]
14-4
(\a -> (a,2*a,3*a)) 5
(5,10,15)
(\xs -> let n = div (length xs) 2 in (take n xs,drop n xs)) "Bom dia"
("Bom", " dia")
14.2.4 Exercícios
Tarefa 14.1
Escreva uma função anônima que recebe uma tripla formada pelo nome, peso e altura de uma pessoa e
resulta no seu índice de massa corporal, dado pela razão entre o peso e o quadrado da altura da pessoa.
Tarefa 14.2
Escreva uma expressão para selecionar (filtrar) os elementos múltiplos de 3 em uma lista de números. Utilize
a função filter :: (a -> Bool) -> [a] -> [a] do prelúdio. Especifique a função que determina a
propriedade a ser satisfeita pelos elementos selecionados usando uma expressão lambda.
Tarefa 14.3
Tarefa 14.4
( f ◦ g)(x) = f (g(x))
Em Haskell podemos definir uma função para compor duas outras funções dadas como argumentos. O
resultado é uma função: a função composta.
Definia a função composta que recebe duas funções como argumentos e resulta na função composta
das mesmas. Use uma definição local para definir a função resultante:
composta f g = · · ·
where
···
14-5
Tarefa 14.5
1. Escreva outra definição para a função composta usando uma expressão lambda para determinar o
seu resultado. Nesta versão não use definições locais.
2. Determine o tipo mais geral da função composta.
Tarefa 14.6
O módulo Prelude define o operador binário (.) para fazer composição de funções. Este operador tem
precedência 9 e associatividade à direira:
infixr 9 .
(.) :: (b -> c) -> (a -> b) -> a -> c
Determine o tipo e o valor das seguintes expressões que usam composição de funções e expressões
lambda:
• Uma função com múltiplos argumentos pode também ser considerada como uma função que retorna outra
função como resultado.
A função f’ recebe um argumento inteiro x e resulta na função h, que por sua vez recebe um argumento
inteiro y e calcula 2*x + y.
• Aplicando a função:
14-6
f’ 2 3
(f’ 2) 3
h 3
2*2 + 3
7
• As funções f e f’ produzem o mesmo resultado final, mas f foi definida de uma forma mais breve.
Da mesma forma que f’, a função f’’ recebe um argumento inteiro x e resulta em uma função. Esta função
recebe um argumento inteiro y e calcula 2*x + y.
• Aplicando a função:
f’’ 2 3
(f’’ 2) 3
(\y -> 2*2 + y) 3
2*2 + 3
7
• Aplicando a função:
f’’’ 2 3
(\x -> (\y -> 2*x + y)) 2 3
(\y -> 2*2 + y) 3
2*2 + 3
7
• Todas as versões apresentadas para a função f (f, f’, f’’ e f’’’) são equivalentes.
• Portanto a função f pode ser considerada como uma função que recebe um argumento e resulta em outra
função que, por sua vez, recebe outro argumento e resulta na soma do dobro do primeiro argumento com o
segundo argumento.
let g = f 5 in (g 8, g 1)
(18,11)
map (f 2) [1,8,0,19,5]
[5,12,4,23,9]
(f 2 . length) "entendeu?"
13
14-7
• Outro exemplo: multiplicação de três números:
• Na verdade mult recebe um argumento de cada vez. Ou seja, mult recebe um inteiro x e resulta em uma
função que por sua vez recebe um inteiro y e resulta em outra função, que finalmente recebe um inteiro z e
resulta no produto x * y * z.
• Este entendimetno fica claro quando usamos expressões lambda para definir a função de maneira alternativa:
14.4 Currying
• Neste caso fica claro que haverá um único argumento, que é a estrutura de dados.
A função somaPar recebe um único argumento que é um par, e resulta na soma dos componentes do par.
• Funções que recebem os seus argumentos um por vez são chamadas de funções curried 1 , celebrando o
trabalho de Haskell Curry no estudo de tais funções.
• Por exemplo:
div 100 :: Integral a => a -> a -- função que divide 100 pelo seu argumento
1
Funções curried às vezes são chamadas de funções currificadas em português.
14-8
14.4.3 Convenções sobre currying
• Para evitar excesso de parênteses ao usar funções curried, duas regras simples foram adotadas na linguagem
Haskell:
• A seta -> (construtor de tipos função) associa-se à direita.
• Exemplo:
significa
mult x y z
significa
((mult x) y) z
• A menos que seja explicitamente necessário o uso de tuplas, todas as funções em Haskell são normalmente
definidas na forma curried.
14.5.1 Operadores
• Um operador binário infixo é uma função de dois argumentos escrita em notação infixa, isto é, entre os seus
(dois) argumentos, ao invés de precedê-los.
• Por exemplo, a função (+) do prelúdio, para somar dois números, é um operador infixo, portanto deve ser
escrita entre os operandos:
3 + 4
• Lexicalmente, operadores consistem inteiramente de símbolos, em oposição aos identificadores normais que
são alfanuméricos.
• Haskell não tem operadores prefixos, com exceção do menos (-), que pode ser tanto infixo (subtração)
como prefixo (negação).
• Por exemplo:
• Um identificador alfanumérico pode ser usado como operador infixo quando escrito entre sinais de crase (’).
• Por exemplo, a função div do prelúdio calcula o quociente de uma divisão inteira:
div 20 3 6
20 ‘div‘ 3 6
14-9
• Um operador infixo (escrito entre seus dois argumentos) pode ser convertido em uma função curried normal
(escrita antes de seus dois argumentos) usando parênteses.
• Exemplos:
1 + 2 3
(+) 1 2 3
• Haskell oferece uma notação especial para a aplicação parcial de um operador infixo, chamada de seção
do operador. Uma seção de um operador é escrita colocando o operador e o argumento desejado entre
parênteses.
• Exemplo:
(1+)
\x -> 1 + x
(1+) 8 9
• Exemplo:
(*2)
\x -> x * 2
(*2) 8 16
• Exemplo:
(100>)
14-10
é a função que verifica se 100 é maior que o seu argumento. É o mesmo que
(100>) 8 True
• Exemplo:
(<0)
\x -> x < 0
(<0) 8 False
(1+) 2 3
(+1) 2 3
(⊕)
(x ⊕)
(⊕ y)
(⊕) = \x y -> x ⊕ y
(x ⊕) = \y -> x ⊕ y
(⊕ y) = \x -> x ⊕ y
• Nota:
– Como uma exceção, o operador bináro - para subtração não pode formar uma seção direita
(-x)
14-11
– A função subtract do prelúdio é fornecida para este fim. Em vez de escrever (-x), você deve escrever
(subtract x)
(subtract 8) 10 2
seção descrição
(1+) função sucessor
(1/) função recíproco
(*2) função dobro
(/2) função metade
• Seções são necessárias para passar operadores como argumentos para outras funções.
• Exemplo:
A função and do prelúdio, que verifica se todos os elementos de uma lista são verdadeiros, pode ser definida
como:
onde foldr é uma função do prelúdio que reduz uma lista de valores a um único valor aplicando uma operação
binária aos elementos da lista.
soma x y = x + y
isto é, soma é uma função que recebe um argumento x e resulta em uma função que por sua vez recebe um
argumento y e resulta em x+y.
14-12
soma
\x -> (\y -> x + y)
soma 2
(\x -> (\y -> x + y)) 2
\y -> 2 + y
soma 2 3
(\x -> (\y -> x + y)) 2 3
(\y -> 2 + y) 3
2 + 3
5
• Expressões lambda também são úteis na definição de funções que retornam funções como resultados.
• Exemplo:
A função const definida na biblioteca retorna como resultado uma função constante, que sempre resulta em
um dado valor:
const 6 0 6
const 6 1 6
const 6 2 6
const 6 9 6
const 6 75 6
h = const 6 \_ -> 6
h 0 6
h 4 6
h 75 6
A função const pode ser definida de uma maneira mais natural usando expressão lambda, tornando explícito
que o resultado é uma função:
• Expressões lambda podem ser usadas para evitar a nomeação de funções que são referenciados apenas
uma vez.
• Exemplo:
A função
que recebe um número n e retorna a lista dos n primeiros números ímpares, pode ser simplificada:
14-13
14.6.3 Exercícios
Tarefa 14.7
a) (’c’:)
b) (:"fim")
c) (==2)
d) (++"\n")
e) (^3)
f) (3^)
g) (‘elem‘ "AEIOU")
Tarefa 14.8
Tarefa 14.9
mult x y z = x * y * z
14-14
14.7 Soluções
Tarefa 14.1 on page 14-5: Solução
composta f g = h
where
h x = f (g x)
1. ’M’ :: Char
2. True :: Bool
3. True :: Bool
4. False :: Bool
14-15
d) (++"\n") :: String -> String
acrescenta uma mudança de linha no final do argumento
23
14-16
15 Funções de Ordem Superior
Resumo
Uma função é conhecida como função de ordem superior quando ela tem uma função como argu-
mento ou resulta em uma função. Nesta aula vamos aprender sobre funções de ordem superior.
Sumário
15.1 Funções de Ordem Superior . . . . . . . . . . . . . . . . . . . . . . . . . . 15-1
15.2 Um operador para aplicação de função . . . . . . . . . . . . . . . . . . . . 15-1
15.3 Composição de funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15-2
15.4 A função filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15-3
15.5 A função map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15-3
15.6 A função zipWith . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15-4
15.7 As funções foldl e foldr, foldl1 e foldr1 . . . . . . . . . . . . . . . . . 15-4
15.7.1 foldl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15-4
15.7.2 foldr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15-5
15.7.3 foldl1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15-5
15.7.4 foldr1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15-6
15.8 List comprehension . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15-6
15.8.1 List comprehension . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15-6
15.8.2 List comprehension e funções de ordem superior . . . . . . . . . . . . 15-7
15.9 Cupom fiscal do supermercado . . . . . . . . . . . . . . . . . . . . . . . . . 15-8
15.10Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15-13
O operador ($) definido no prelúdio se destina a substituir a aplicação de função normal, mas com uma
precedência e associatividade diferente para ajudar a evitar parênteses. O operador ($) tem precedência zero
e associa-se à direita. Já a aplicação de função normal tem precedência maior que todos os operadores e associa-
se à esquerda. O operador ($) é usado principalmente para eliminar o uso de parênteses nas aplicações de
funções.
15-1
Exemplos de aplicação de função com ($)
sqrt 36 6.0
sqrt $ 36 6.0
($) sqrt 36 6.0
head (tail "asdf") ’a’
head $ tail $ "asdf" ’a’
head $ tail "asdf" ’a’
even (succ (abs (negate 36))) False
even $ succ $ abs $ negate 36 False
Definição de ($)
infixr 0 $
f $ x = f x
Definição de (.)
infixr 9 .
f . g = h
where h x = f (g x)
15-2
15.4 A função filter
A função filter do prelúdio recebe uma função e uma lista como argumentos, e seleciona (filtra) os elementos
da lista para os quais a função dada resulta em verdadeiro.
Note que filter é uma função de ordem superior, pois recebe outra função como argumento.
Importando um módulo
A função isDigit não faz parte do módulo Prelude, mas está definida no módulo Data.Char. Para usar
isDigit é necessário importar o módulo Data.Char:
• no ambiente interativo use o comando :module (ou simplesmente :m):
:m + Data.Char
import Data.Char
Definição de filter
filter _ [] = []
filter f (x:xs) | f x = x : filter f xs
| otherwise = filter f xs
15-3
Definição de map
map _ [] = []
map f (x:xs) = f x : map f xs
zipWith (+) [] [] []
zipWith (+) [1,2,3,4,5] [3,3,4,1,5] [4,5,7,5,10]
zipWith (++) ["AB","cde"] ["?","123"] ["AB?","cd123"]
zipWith (^) [5,6,7,8] [2,3,4,5] [25,216,2401,32768]
zipWith (*) [5,6,7,8] [2,3] [10,18]
Definição de zipWith
15.7.1 foldl
foldl reduz uma lista, usando uma função binária e um valor inicial, de forma associativa à esquerda.
foldl (+) 0 [] 0
foldl (+) 0 [1] 1
foldl (+) 0 [1,2] 3
foldl (+) 0 [1,2,4] 7
foldl (*) 1 [5,2,4,10] 400
foldl (&&) True [2>0,even 6,odd 5,null []] True
foldl (||) False [2>3,even 6,odd 5,null []] True
15-4
Definição
foldl f z [] = z
foldl f z (x:xs) = foldl f (f z x) xs
15.7.2 foldr
foldr reduz uma lista, usando uma função binária e um valor inicial, de forma associativa à direita.
foldr (+) 0 [] 0
foldr (+) 0 [1] 1
foldr (+) 0 [1,2] 3
foldr (+) 0 [1,2,4] 7
foldr (*) 1 [5,2,4,10] 400
foldr (&&) True [2>0,even 6,odd 5,null []] True
foldr (||) False [2>3,even 6,odd 5,null []] True
Definição
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
15.7.3 foldl1
foldl1 reduz uma lista não vazia usando uma função binária, de forma associativa à esquerda.
foldl1 é uma variante de foldl que não tem valor inicial, e portanto deve ser aplicada a listas não-vazias.
15-5
Definição
15.7.4 foldr1
foldr1 reduz uma lista não vazia usando uma função binária, de forma associativa à esquerda.
foldr1 é uma variante de foldr que não tem valor inicial, e portanto deve ser aplicada a listas não-vazias.
Definição
foldr1 _ [x] = x
foldr1 f (x:xs) = f x (foldr1 f xs)
é a lista [1,4,9,16,25] de tdos os números x^2 tal que x é um elmento da lista [1,2,3,4,5].
A frase x <- [1..5] é chamada gerador, já que ela informa como gerar valores para a variável x. Compre-
ensões podem ter múltiplos geradores, separados por vírgula. Por exemplo:
Se a ordem dos geradores for trocada, a ordem dos elementos na lista resultante também é trocada. Por exemplo:
15-6
Geradores múltiplos são semelhantes a loops aninhados: os últimos geradores são como loops mais profun-
damente aninhados cujas variáveis mudam mais freqüentemente. No exemplo anterior, como x <- [1,2,3] é o
último gerador, o valor do componente x de cada par muda mais frequentemente.
Geradores posteriores podem depender de variáveis introduzidas em geradores anteriores. Por exemplo:
List comprehensions podem usar guardas para restringir os valores produzidos por geradores anteriores. Por
exemplo:
divisores 15 [1,3,5,15]
divisores 120 [1,2,3,4,5,6,8,10,12,15,20,24,30,40,60,120]
Um número inteiro positivo é primo se seus únicos divisores são 1 e ele próprio. Assim, usando divisores, podemos
definir uma função que decide se um número é primo:
primo 15 False
primo 7 True
Usando um guarda agora podemos definir uma função que retorna a lista de todos os números primos até um deter-
minado limite:
primos 40 [2,3,5,7,11,13,17,19,23,29,31,37]
primos 12 [2,3,5,7,11]
15-7
[ x^2 | x <- [1..5] ] [1,4,9,16,25]
map (^2) [1..5] [1,4,9,16,25]
[1234,4719,3814,1112,1113,1234]
Esta lista deve ser convertida para uma conta como mostra a figura a seguir:
Haskell Stores
Total....................13.90
Primeiro devemos decidir como modelar os objetos envolvidos. Códigos de barra e preços (em centavos) po-
dem ser representados por números inteiros, e nomes de mercadorias podem ser representados por strings. Então
usaremos os seguintes tipos:
A conversão dos códigos de barras será baseada em um banco de dados que relaciona códigos de barras,
nomes de mercadorias, e preços. Usaremos uma lista para representar o banco de dados de mercadorias:
tabelaMercadorias :: Mercadorias
tabelaMercadorias = [ (4719, "Fish Fingers", 121 )
, (5643, "Nappies", 1010)
, (3814, "Orange Jelly", 56 )
, (1111, "Hula Hoops", 21 )
, (1112, "Hula Hoops (Giant)", 133 )
, (1234, "Dry Sherry, 1lt", 540 )
]
O objetivo do programa é primeiramente converter uma lista de códigos de barra em uma lista de pares (Nome,Preco)
por meio de uma consulta à tabela de mercadorias. Em seguida esta lista de pares deve ser convertida em uma string
para exibição na tela. Usaremos as seguintes definições de tipo:
15-8
type Carrinho = [Codigo]
type Conta = [(Nome,Preco)]
para representar um carrinho de compras e uma conta (cupom fiscal) corresponde a uma compra.
Defina uma função formataCentavos :: Preco -> String que recebe o preço em centavos e resulta
em uma string representando o preço em reais.
Por exemplo:
Use as funções div, mod e show. Observe que ao dividir o preço em centavos por 100, o quociente
corresponde à parte inteira do preço em reais, e o resto corresponde à parte fracionária do preço em reais.
Preste atenção no caso do resto menor do que 10: deve-se inserir um 0 à esquerda explicitamente.
Defina uma função formataLinha :: (Nome,Preco) -> String que recebe um par formado pelo nome
e preço de uma mercadoria e resulta em uma string representando uma linha da conta do supermercado.
Por exemplo:
O tamanho de uma linha em uma conta deve ser 30. Use a variável abaixo para representar este valor.
tamanhoLinha :: Int
tamanhoLinha = 30
Use as funções (++), show, length e replicate do prelúdio, e a função formataCentavos da tarefa
15.1.
A função replicate :: Int -> a -> [a] recebe um número inteiro n e um valor x e resulta em
uma lista de comprimento n onde todos os elementos são x . Por exemplo:
replicate 5 13 [13,13,13,13,13]
replicate 8 ’.’ "........"
15-9
Tarefa 15.3: Formatação de várias linhas do cupom fiscal
Defina a função formataLinhas :: [(Nome,Preco)] -> String que recebe uma lista de pares forma-
dos pelos nomes das mercadorias e seus respectivos preços em uma compra, e resulta na string correspon-
dente ao corpo da conta do supermercado.
Por exemplo:
Use a função formataLinha da tarefa 15.3 para obter as linhas correspondentes a cada produto, e
concatene estas linhas usando a função (++) do prelúdio. Não use recursividade explícita, mas use as
funções map e foldr ou foldl do prelúdio. Alternativamente você poderá usar list comprehension.
Defina a função formataTotal :: Preco -> String que recebe o valor total da compra, e resulta em
uma string representado a parte final da conta do supermercado.
Por exemplo:
15-10
Tarefa 15.5: Formatação do cupom fiscal
Defina a função formataConta :: Conta -> String que recebe a lista dos itens comprados e resulta
na string representando a conta do supermercado, já formatada.
Por exemplo:
Haskell Stores
Total....................13.90
Defina a função calculaTotal :: Conta -> Preco que recebe uma conta (lista de pares formados pelo
nome e preço das mercadorias de uma compra), e resulta no preço total da compra.
Por exemplo:
Não use recursividade explícita, mas use as funções map e sum do prelúdio.
Defina uma função procuraCodigo :: Mercadorias -> Codigo -> (Nome,Preco) que recebe o banco
de dados com os nomes e preços das mercadorias disponíveis no supermercado e o código de barras da
mercadoria comprada, e resulta no par formado pelo nome e pelo preço da mercadoria, de acordo com
o banco de dados. Se o código de barras não constar no banco de dados, o resultado deve ser o par
("Unknown Item",0).
Por exemplo:
15-11
Tarefa 15.8: Criação da conta da compra
Defina a função criaConta :: Mercadorias -> Carrinho -> Conta que recebe o banco de dados
com os nomes e preços das mercadorias disponíveis no supermercado, e a lista de códigos de barra corres-
pondente a uma compra, e resulta na lista dos pares (Nome,Preco) para as mercadorias compradas.
Por exemplo:
Use uma aplicação parcial da função procuraCodigo definida na tarefa 15.7 e a função map do prelúdio.
Não use recursão explícita.
Defina a função fazCompra :: Mercadorias -> Carrinho -> String que recebe o banco de dados
com os nomes e preços das mercadorias disponíveis no supermercado, e a lista de códigos de barra corres-
pondente a uma compra, e resulta na string correspondente à nota da compra.
Use a função criaConta (definida na tarefa 15.8) para criar a conta a partir dos argumentos, e a função
formataConta (definida na tarefa 15.5) para converter a conta para string. Use composição de funções.
Defina a variável main :: IO () como uma ação de entrada e saída que interage com o usuário. Quando
main for executada, o usuário deve digitar os códigos de barras das mercadorias compradas e em seguida
a conta do supermercado deve ser exibida na tela.
Para fazer a entrada de um valor (digitado pelo usuário) você pode usar a função readLn do prelúdio.
Tarefa 15.11
Complete a aplicação com a definição do módulo Main contendo as definições feitas anteriormente, e expor-
tando a variável main. Compile a aplicação gerando um programa executável. Teste a aplicação.
Se necessário, importe stdout, hSetBuffering, BufferMode e NoBuffering do módulo System.IO
e cancele a bufferização da saída padrão.
15-12
15.10 Soluções
tabelaMercadorias :: Mercadorias
tabelaMercadorias =
[ (4719, "Fish Fingers", 121 )
, (5643, "Nappies", 1010)
, (3814, "Orange Jelly", 56 )
, (1111, "Hula Hoops", 21 )
, (1112, "Hula Hoops (Giant)", 133 )
, (1234, "Dry Sherry, 1lt", 540 )
]
tamanhoLinha :: Int
tamanhoLinha = 30
15-13
(replicate qtdepts ’.’) ++
precoStr
where
precoStr = formataCentavos preco
qtdepts = tamanhoLinha - length "Total" - length precoStr
leCodigos :: IO [Codigo]
leCodigos = do x <- readLn
if x == 0
then return []
else do xs <- leCodigos
return (x:xs)
main :: IO ()
main = do hSetBuffering stdout NoBuffering
putStrLn "Emissão de cupom fiscal v1.0"
putStrLn "===================================="
putStrLn ""
putStrLn "Digite os códigos de barra das mercadorias (0 termina):"
codigos <- leCodigos
putStrLn "===================================="
putStr (fazCompra tabelaMercadorias codigos)
putStrLn "===================================="
15-14
16 Argumentos da Linha de Comando e Arquivos
Resumo
Nesta aula vamos aprender a escrever aplicações que obtém dados de arquivos e que gravam os
resultados calculados em arquivos. Vamos também aprender a usar argumentos passados para um
programa na linha de comando.
Sumário
16.1 Argumentos da linha de comando . . . . . . . . . . . . . . . . . . . . . . . 16-1
16.2 Encerrando o programa explicitamente . . . . . . . . . . . . . . . . . . . . 16-2
16.3 Formatando dados com a função printf . . . . . . . . . . . . . . . . . . . 16-4
16.4 Arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16-5
16.5 As funções lines e unlines, e words e unwords . . . . . . . . . . . . . . . 16-6
16.6 Exemplo: processar notas em arquivo . . . . . . . . . . . . . . . . . . . . . 16-7
16.7 Problemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16-8
16.8 Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16-10
A ação getArgs
getArgs :: IO [String]
A ação de E/S getArgs (definida no módulo System.Environment), quando executada, retorna uma lista formada
pelos argumentos da linha de comando do programa.
A ação getProgName
getProgName :: IO String
A ação de E/S getProgName (definida no módulo System.Environment), quando executada, retorna o nome do
programa.
args.hs
16-1
module Main (main) where
main =
do progName <- getProgName
putStr "The program name is ...: "
print progName
args <- getArgs
putStr "The arguments are......: "
print args
$ ./args a b c -o test
The program name is ...: "args"
The arguments are......: ["a","b","c","-o","test"]
Quando o programa for executado no ambiente interativo, o nome do programa e os argumentos a serem usados
pelo programa podem ser especificados usando os comandos :set prog e :set args, respectivamente.
Exemplo de execução da aplicação
*Main> main
The program name is ...: "<interactive>"
The arguments are......: []
*Main> main
The program name is ...: "test-args"
The arguments are......: ["-pdf","entrada.txt","saida.txt"]
O tipo ExitCode
Quando a execução de um programa termina, o ambiente onde o programa foi executado (normalmente o sistema
operacional) recebe um código (status) de retorno. Este código pode ser inspecionado pelo ambiente de execução
para verificar em que condições a execução do programa terminou. Tipicamente o valor zero indica sucesso, e um
valor diferente de zero indica falha.
O tipo ExitCode define códigos de saída que podem ser retornados por um programa quando ele é encerrado.
Este tipo possui dois construtores de dados:
• ExitSuccess: indica término com sucesso.
• ExitFailure Int: indica falha com um código de saída; a interpretação exata do código é dependente do
sistema operacional.
16-2
A função exitWith
A função exitWith pode ser usada para criar uma ação de E/S que, quando executada, termina o programa com o
código de saída especificado.
exitFailure :: IO ()
A ação de E/S exitFailure, quando executada, termina o programa com um código de falha que é dependente da
implementação.
exitSuccess :: IO ()
A ação de E/S exitSuccess, quando executada, termina o programa com uma indicação de sucesso.
main =
do args <- getArgs
case args of
[input,output] ->
do putStrLn ("Entrada: " ++ input)
putStrLn ("Saída : " ++ output)
_ ->
do progName <- getProgName
putStrLn ("Chamada inválida do programa " ++ progName)
putStrLn ("Uso: " ++ progName ++ " <arquivo-entrada> <arquivo-saída>")
exitFailure
16-3
16.3 Formatando dados com a função printf
A função printf (definida no módulo Text.Printf ) formata um número variável de argumentos usando uma string
de formatação no estilo da função printf da linguagem C. O resultado pode ser de qualquer tipo que seja instância
da classe PrintfType, que inclui os tipos String e IO a.
A string de formatação consiste de caracteres comuns e especificações de conversão, que podem especificar
como formatar um dos argumentos na string de saída. Uma especificação de formato é introduzida pelo caracter %
e termina com um caracter de formato que é a principal indicação de como o valor deve ser formatado. Use %% para
inserir o próprio caracter % na string de formatação. O restante da specificação de conversão é opcional, podendo
ser caractares de flag, espeficador de tamanho, specificador de precisão, e caracteres modificadores de tipo, nesta
ordem.
Caracteres de flag
Formas alternativas
Tamanho de campo
Precisão
.num precisão
. o mesmo que .0
.* precisão tomada da lista de argumentos
O signifcado da precisão depende do tipo de conversão:
Integral número mínimo de dígitos a serem exibidos
RealFloat número de dígitos depois do ponto decimal
String número máximo de caracteres
16-4
Modificadores de tamanho
hh Int8
h Int16
l Int32
ll Int64
L Int64
Caracteres de formatação
c caracter Integral
d decimal Integral
o octal Integral
x hexadecimal Integral
X hexadecimal Integral
b binário Integral
u decimal sem sinal Integral
f ponto flutuante RealFloat
F ponto flutuante RealFloat
g ponto flutuante geral RealFloat
G ponto flutuante geral RealFloat
e ponto flutuante com expoente RealFloat
E ponto flutuante com expoente RealFloat
s string String
v padrão qualquer tipo
Exemplos
16.4 Arquivos
Haskell possui várias definições para manipular arquivos definidas no módulo System.IO. Algumas delas são
mencionadas a seguir.
16-5
type FilePath = String
A função lines
A função lines divide uma string em uma lista de strings nas mudanças de linha. As strings resultantes não contem
o caracter de mudança de linha.
Por exemplo:
lines "aa\nbb\nbb\n\nzz\n"
["aa","bb","bb","","zz"]
lines "1234 Pedro 1.5 1.7\n1111 Carla 6.2 7.0\n2121 Rafael 8.1 8.8"
["1234 Pedro 1.5 1.7","1111 Carla 6.2 7.0","2121 Rafael 8.1 8.8"]
A função unlines
A função unlines é uma operação inversa de lines. Ela junta as strings da lista dada após acrescentar o caracter
de mudança de linha no final de cada uma delas.
Por exemplo:
unlines ["aa","bb","bb","zz"]
"aa\nbb\nbb\nzz\n"
A função words
A função words divide uma string em uma lista de strings nos caracteres brancos (espaço, tabulação, mudança de
linha, etc). As strings resultantes não contem caracteres brancos.
Por exemplo:
16-6
A função unwords
A função unwords é uma operação inversa de lines. Ela junta as strings da lista dada acrescentando um espaço
entre elas.
Por exemplo:
unlines ["aa","bb","bb","zz"]
"aa bb bb zz"
Tarefa 16.1
Criar um programa para ler de um arquivo os dados dos alunos de uma turma (a matrícula, o nome, a
nota na primeira avaliação, e a nota na segunda avaliação), calcular a média aritmética das notas das duas
avaliações, e determinar a situação de cada aluno, gravando os resultados em outro arquivo.
A situação do aluno é dada pela tabela seguinte
Os nomes dos arquivos de entrada e saída devem ser informados como argumentos da linha de co-
mando.
Exemplo de arquivo de entrada:
16-7
module Main (main) where
main :: IO ()
main =
do argumentos <- getArgs
case argumentos of
[entrada, saida] -> do conteudo <- readFile entrada
writeFile saida (processa conteudo)
_ -> exitFailure
16.7 Problemas
O arquivos texto boynames.txt e girlnames.txt, que estão disponiveis no sítio da disciplina, contêm
uma lista dos 1.000 nomes de garotos e garotas mais populares nos Estados Unidos para o ano de 2003
como compilados pela Administração do Segurança Social.
Estes arquivos consitem dos nomes mais populares listados por linha, onde o nome mais popular é
listada em primeiro lugar, o segundo nome mais popular é listada em segundo lugar, e assim por diante, até
o 1000 nome mais popular, que é listada por último. Cada linha é composta pelo primeiro nome seguido de
um espaço em branco e, em seguida, do número de nascimentos registrados usando esse nome no ano.
Por exemplo, o arquivo girlnames.txt inicia com
Emily 25494
Emma 22532
Madison 19986
Isso indica que entre as garotas Emily foi o nome mais popular em 2003, com 25.494 nomes registrados,
Emma foi o segundo mais popular, com 22.532 registros, e Madison foi o terceiro mais popular, com 19.986
registros.
Escreva um programa que lê os arquivos com os dados dos garotas e das garotas e em seguida, permita
que o usuário insira um nome. O programa deve pesquisar ambas as listas de nomes. Se houver uma cor-
respondência, então ele deve emitir o classificação de popularidade e o número de nascimentos registrados
com este nome. O programa deve também indicar se não houver correspondência.
Por exemplo, se o usuário digita o nome Justice, o programa deve produzir a saída
16-8
Justice é classificado como 456 em popularidade entre garotas com 655 registros.
Justice é classificado como 401 em popularidade entre garotos com 653 registros.
Walter não está classificado entre os 1000 nomes mais populares de garotas.
Walter é classificado como 356 em popularidade entre garotos com 775 registros.
Dicas:
1. Defina uma função recursiva tabela para fazer a análise de um texto (string) contendo uma tabela
de nomes, obtendo como resultado uma lista de triplas onde cada tripla é formada pela posição, pelo
nome, e pela quantidade de registros.
Por exemplo:
2. Defina uma função pesquisa que recebe uma string descrevendo a tabela de nomes usada, uma
lista de triplas formadas pela posição, pelo nome (a tabela de nomes), e pela quantidade de registros
de uma tabela de nomes, e um nome (string) a ser pesquisado na tabela. O resultado da função deve
ser uma ação de E/S que, quando executada, pesquisa o nome na lista e exibe o resultado na saída
padrão, retornando a tupla vazia.
Por exemplo:
pesquisa
"garotas"
[(1,"ana",1234),(2,"paula",561),(3,"beatriz",180)]
"paula"
,→
ana e classificado como 2 em popularidade entre garotas com 561 registros
pesquisa
"garotas"
[(1,"ana",1234),(2,"paula",561),(3,"beatriz",180)]
"Maria"
,→
Maria nao esta classificado entre os 3 nomes mais populares de garotas.
3. Defina uma função go que recebe as duas tabelas de nomes e resulta em uma ação de E/S que,
quando executada:
• solicita ao usuário para digitar o nome a ser pesquisado,
• lê o nome,
• analisa o nome lido
– se for a string vazia, retorna a tupla vazia
– caso contrário:
∗ pesquisa o nome entre os garotos e exibe o resultado da pesquisia,
∗ pesquisa o nome entre as garotas e exibe o resultado da pesquisia, e
∗ chama go recursivamente para continuar a interação com o usuário.
4. Defina a ação main para fazer o sequenciamento:
• desligar a bufferização da saída padrão,
• ler o arguivo com a tabela de nomes de garotos,
• ler o arguivo com a tabela de nomes de garotas, e
• interagir com o usuário usando a função go.
16-9
16.8 Soluções
Tarefa 16.2 on page 16-8: Solução
main :: IO ()
main =
do hSetBuffering stdout NoBuffering
texto1 <- readFile "boynames.txt"
texto2 <- readFile "girlnames.txt"
go (tabela texto1) (tabela texto2)
16-10
17 Tipos Algébricos
Resumo
Um tipo algébrico é um tipo onde são especificados a forma de cada um dos seus elementos.
Algébrico se refere à propriedade de que um tipo algébrico é criado por operações algébricas. A
álgebra aqui é somas e produtods:
• soma é a alternância: A|B significa A ou B , mas não ambos, e
• produto é a combinação: AB significa A e B juntos.
Somas e produtos podem ser combinados repetidamente em estruturas arbitrariamente largas.
Nesta aula vamos aprender como definir e usar tipos algébricos (ou seja, estruturas de dados), em
Haskell.
Sumário
17.1 Novos tipos de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17-1
17.2 Tipos algébricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17-2
17.3 Exemplo: formas geométricas . . . . . . . . . . . . . . . . . . . . . . . . . 17-2
17.4 Exemplo: sentido de movimento . . . . . . . . . . . . . . . . . . . . . . . . 17-3
17.5 Exemplo: cor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17-5
17.6 Exemplo: coordenadas cartesianas . . . . . . . . . . . . . . . . . . . . . . . 17-5
17.7 Exemplo: horário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17-6
17.8 Exemplo: booleanos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17-6
17.9 Exemplo: listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17-7
17.10Exercícios básicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17-7
17.11Números naturais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17-8
17.12Árvores binárias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17-9
17.13O construtor de tipo Maybe . . . . . . . . . . . . . . . . . . . . . . . . . . . 17-9
17.14Exercício: lógica proposicional . . . . . . . . . . . . . . . . . . . . . . . . . 17-10
17.15Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17-14
– Bool
– Char
– Int
– Integer
– Float
– Double
• Tipos Compostos:
17-1
– tuplas: (t 1 , t 2 , ..., t n )
– listas: [t]
– funções: t 1 -> t 2
– dias da semana
– estações do ano
– figuras geométricas
– árvores
– ...
onde:
• cx é um contexto
• T é o construtor de tipo
• t i j são tipos
• Construtores de tipo e construtores de dados são identificadores alfanuméricos começando com letra maiús-
cula, ou identificadores simbólicos.
– construir valores do tipo definido, funcionando como uma função (eventualmente, constante) que re-
cebe argumentos (do tipo indicado para o construtor), e constrói um valor do novo tipo de dados;
• Construtores de dados são funções especiais, pois não tem nenhuma definição (algoritmo) associada.
17-2
• Os construtores de dados deste tipo são:
a :: Figura
a = Circulo 2.3 -- um círculo de raio 2.3
b :: Figura
b = Retangulo 2.8 3.1 -- um retângulo de base 2.8 e altura 3.1
lfig :: [Figura]
lfig = [Retangulo 5 3, Circulo 5.7, Retangulo 2 2]
• Expressões como Circulo 2.3 ou Retangulo 2.8 3.1 não podem ser reduzidas, pois já estão em
sua forma mais simples.
• Os construtores são utilizados em casamento de padrões para acessar os componentes de um valor do tipo
algébrico.
• Podemos definir funções envolvendo os tipos algébricos.
17-3
• Os construtores de dados deste tipo, todos constantes, são:
Esquerda :: Sentido
Direita :: Sentido
Acima :: Sentido
Abaixo :: Sentido
• Quando os construtores de dados são constantes, (ou seja, não tem argumentos), dizemos que o tipo é uma
enumeração.
• Neste exemplo os únicos valores do tipo Sentido são Direita, Esquerda, Acima e Abaixo.
• Podemos definir funções envolvendo o tipo algébrico:
Oops!
• A princípio Haskell não sabe como exibir valores dos novos tipos.
• O compilador pode definir automaticamente funções necessárias para exibição:
• A cláusula deriving permite declarar as classes das quais o novo tipo será instância, automaticamente.
• Logo, segundo a declaração dada, o tipo Sentido é uma instância da classe Show, e a função show é
sobrecarregada para o tipo Sentido.
17-4
17.5 Exemplo: cor
• Definição de um novo tipo para representar cores:
Azul :: Cor
Amarelo :: Cor
Verde :: Cor
Vermelho :: Cor
17-5
17.7 Exemplo: horário
• Definição de um novo tipo para representar horários:
e podem ser vistos como uma etiqueta (tag) que indica de que forma os argumentos a que são aplicados
devem ser entendidos.
True :: Bool
False :: Bool
infixr 3 &&
(&&) :: Bool -> Bool -> Bool
True && True = True
_ && _ = False
infixr 3 ||
(||) :: Bool -> Bool -> Bool
False || False = False
_ || _ = True
17-6
17.9 Exemplo: listas
– Nil :: Lista a
um construtor constante representando a lista vazia
• Exemplo: a lista do tipo Lista Int formada pelos elementos 3, 7 e 1 é representada por
Cons 3 (Cons 7 (Cons 1 Nil)).
• O construtor de tipo Lista está parametrizado com uma variável de tipo a, que poderá ser substituída por
um tipo qualquer. É neste sentido que se diz que Lista é um construtor de tipo.
• O tipo Lista a deste exemplo é similar ao tipo [a] da bilioteca padrão do Haskell:
– um identificador simbólico com status de operador infixo para o construtor de lista não vazia: (:)
Defina uma função para calcular o perímetro de uma forma geométrica do tipo Figura. Qual é o tipo desta
função?
17-7
Tarefa 17.2: Item do supermercado
2. Defina uma função que recebe uma lista de ítens como argumento e resulta no valor total a ser pago
pelos ítens na lista. Escreva a assinatura de tipo da função.
Adicione um novo construtor de dados ao tipo Figura para triângulos, e extenda as funções eRedondo,
area e perimetro para incluir triângulos.
Dicas:
A=
p
p(p − a)(p − b)(p − c)
Defina uma função para verificar se uma figura é regular. São figuras regulares: o círculo, o quadrado, o
triângulo equilátero.
2. Escreva uma função que receba uma identifcação de casa (de acordo com o item anterior) e dê a sua
representação textual (isto é, a função deve converter para uma string).
• zero, ou
• positivo, sendo neste caso o sucessor de outro número natural
O seu tipo deve ter dois construtores de dados: Zero, um construtor constante, para representar o
valor zero, e Suc, um construtor de aridade um, para representar um número positivo.
Observe que o tipo Nat deve ser recursivo, já que ele deverá ser usado em sua própria definição.
Use derivação automática da classe Show.
17-8
2. Defina as variávies um, dois e tres do tipo Nat cujos valores são os números naturais 1, 2 e 3,
respectivamente.
3. Defina a função nat2integer :: Nat -> Integer que converte um número natural em um nú-
mero inteiro. Faça uma definição recursiva onde o caso base corresponde 0, e o caso recursivo
corresponde aos números positivos.
4. Defina a função integer2nat :: Integer -> Nat que converte um número inteiro em um nú-
mero natural.
5. Defina a função natLt :: Nat -> Nat -> Bool que recebe dois números naturais e verifica se o
primeiro é menor que o segundo.
6. Defina a função natAdd :: Nat -> Nat -> Nat que recebe dois números naturais e resulta na
soma dos números. A função deve ser recursiva no segundo argumento.
7. Defina a função natSub :: Nat -> Nat -> Nat que recebe dois números naturais e resulta na
diferença dos números. A função deve ser recursiva no segundo argumento.
8. Defina a função natMul :: Nat -> Nat -> Nat que recebe dois números naturais e resulta no
produto dos números. A função deve ser recursiva no segundo argumento.
9. Defina as funções natDiv :: Nat -> Nat -> Nat e natMod :: Nat -> Nat -> Nat, que re-
cebem dois números naturais e resultam no quociente e no resto dos números, respectivamente.
Usando tipos algébricos torna-se fácil a representação de estruturas de dados comumente encontradas em
computação. Neste exercício vamos explorar a representação de árvores.
1. Defina um construtor de tipo algébrico BinTree para representar árvores binárias de busca. Uma
árvore binária de busca pode ser
• vazia
• não vazia (nó), formada por um valor qualquer (uma informação armazenada no nó da árvore)
e duas sub-árvores.
O tipo BinTree a será o tipo das árvores binárias de busca que armazenam valores do tipo a em
seus nós. Observe que este tipo será polimórfico e recursivo. Observe ainda que o construtor de
tipo BinTree tem aridade um, ou seja, ele espera um argumento de tipo (correspondente ao tipo dos
valores armazenados nos nós da árvore).
Use derivação automática da classe Show.
2. Defina uma função btLength :: BinTree a -> Int que recebe uma árvore binária de busca e
resulta no número de elementos armazenados na árvore (tamanho da árvore).
3. Defina uma função btDepth :: BinTree a -> Int que recebe uma árvore binária de busca e
resulta na profundidade da árvore.
4. Defina uma função btElem :: a -> BinTree a -> Bool que recebe um valor e uma árvore, e
verifica se o valor é um elemento da árvore.
O construtor de tipo é Maybe, de aridade um, que espera um argumento de tipo representando o tipo do dado
encapsulado pelo construtor de dados Just.
17-9
Os construtores de dados deste tipo são:
Nothing :: Maybe a
Just :: a -> Maybe a
test :: IO ()
test =
do putStrLn "digite dois números"
a <- readLn
b <- readLn
case safediv a b of
Nothing -> do putStrLn "divisão por zero"
putStrLn "tente novamente"
test
Just z -> putStrLn ("resposta: " ++ show z)
A função readMaybe :: Read a => String -> Maybe a, definida no módulo Text.Read, converte
uma string em um valor do tipo a (que deve ser instância da classe Read). A conversão sucede se e so-
mente se há exatamente um resultado válido.
Faça um programa que leia uma temperatura na escala Celsius e calcula e exibe a temperatura corres-
pondente na escala Fahrenheit. O programa deve verificar se a entrada de dados sucede ou falha. Uma
nova entrada deve ser feita enquanto a leitura for inválida.
1. Defina um módulo Prop onde será definido um tipo para representar proposições (sentenças da lógica
proposicional), e algumas operações sobre estas sentenças, como indicado a seguir.
2. Defina um tipo algébrico para representar uma proposição. Uma proposição pode ser:
Defina o tipo das proposições usando o nome Prop para o construtor de tipo, com os seguintes
construtores de dados:
17-10
Cte :: Bool -> Prop -- constantes
Var :: String -> Prop -- variáveis
Neg :: Prop -> Prop -- negação
Con :: Prop -> Prop -> Prop -- conjunção (e)
Dis :: Prop -> Prop -> Prop -- disjunção (ou)
3. Como exercício escreva as seguintes proposições como valores do tipo Prop definido anteriormente:
p1 = a ∨ (b ∧ V )
p2 = a ∨ ¬a
p3 = a ∧ ¬a
p4 = ¬(p ∧ ¬p)
p5 = p ∨ ¬(p ∧ q)
p6 = (p ∧ q) ∧ ¬(p ∨ q)
p7 = (p ∨ q) ∧ (p ∨ r)
4. Para determinar o valor de uma proposição é necessário conhecer o valor das variáveis que ocorrem
na proposição.
Defina um tipo para representar uma memória, isto é, um mapeamento de identificadores (nomes das
variáveis) a valores lógicos.
Dica: Use listas de associações. Uma lista de associação é uma lista de pares onde o primeiro
elemento do par funciona como chave.
5. Defina uma função que recebe uma memória e uma proposição e calcula o valor da proposição usando
a memória. Considere que o valor de uma variável indefinida é falso.
Dica: Para pesquisar o valor de uma variável na memória utilize a função lookup do prelúdio. Esta
função permite encontrar o valor associado a uma chave em uma lista de associações.
6. Defina uma função que recebe uma proposição e resulta na lista das variáveis que ocorrem na ex-
pressão. Cada variável deve ocorrer uma única vez na lista.
Dica: Defina e use uma função auxiliar merge para concontenar duas listas ordenadas, resultando
em uma lista também ordenada, sem repetição de elementos. Por exemplo:
7. Defina uma função booleanos :: Int -> [[Bool]] de forma que booleanos n resulte na lista
de todas as permutações de n valores lógicos.
Por exemplo:
booleanos 3
Dica: Observe na estrutura da lista resultante que booleanos 3 possui duas cópias de booleanos 2,
a primeira precedida do valor False em cada caso, e a segunda precedida pelo valor True em cada
caso:
17-11
False False False
False False True
False True False
False True True
True False False
True False True
True True False
True True True
• no caso base, booleanos 0, o resultado é a lista de todas as listas com zero valores lógicos
(ou seja, a lista contendo apenas a lista vazia), e
• no caso recursivo, booleanos n, tomamos duas cópias da lista produzida por booleanos (n-1),
colocamos False na frente de cada lista da primeira cópia, True em frente de cada lista da
segunda cópia, e concatenamos os resultados.
8. Defina uma função substs :: Prop -> [Memoria] que gera todas as substituições possíveis
para as variávies que ocorrem em uma proposição, obtendo a lista das variáveis, gerando todas
as possíveis listas de valores lógicos para esta quantidade de variáveis, e então zipando a lista de
variáveis com cada uma das memórias.
9. Defina um tipo tautologia :: Prop -> Bool para verificar se uma proposição é uma tautolo-
gia. Basta checar se o valor da proposição é verdadeiro para todas as substituições possíveis das
variáveis.
10. Em um módulo chamado Main defina uma ação de E/S main :: IO () que, quando executada,
solicita ao usuáro para digitar uma expressão boolena, e em seguida informa se a proposição é uma
tautologia ou não.
Você pode utilizar o módulo a seguir para converter uma string em uma proposição.
17-12
module ParseProp where
import Text.Parsec
import Prop
pExp :: Parser
pExp = pDis
pAtm :: Parser
pAtm = try (lexeme (string "V") >> return (Cte True))
<|>
try (lexeme (string "F") >> return (Cte False))
<|>+
try (fmap Var (lexeme (many1 letter)))
<|>
try (between (lexeme (string "(")) (lexeme(string ")")) pExp)
<|>
(lexeme (string "~") >> (fmap Neg pAtm))
pCon :: Parser
pCon = fmap (foldl1 Con) (many1 pAtm)
pDis :: Parser
pDis = fmap (foldl1 Dis) (sepBy1 pCon (lexeme (string "+")))
17-13
17.15 Soluções
17-14
data Ident = Nome String
| Numero Int
deriving (Show)
17-15
module Nat where
data Nat
= Zero
| Suc Nat
deriving (Show)
17-16
module BinTree where
data BinTree a = V
| N a (BinTree a) (BinTree a)
deriving (Show)
a1 = N 12
(N 5
(N 2 V V)
(N 9
(N 6 V V)
V))
(N 20
V
(N 30
(N 25 V V)
(N 31 V V)))
main :: IO ()
main =
do hSetBuffering stdout NoBuffering
putStrLn "Conversão de temperaturas"
putStrLn "==========================="
c <- leTemperatura
let f = 32 + 9/5 * c
putStrLn ("Temperatura em : " ++ show f)
leTemperatura :: IO Double
leTemperatura =
do putStr "Digite a temperatura em ℃: "
entrada <- getLine
case readMaybe entrada of
Just x -> return x
Nothing -> do putStrLn "Seu !!!!!!"
putStrLn "Tente novamente!"
leTemperatura
17-17
18 Classes de Tipos
Resumo
Nesta aula vamos aprender a definir classes de tipos e instâncias de classes de tipos.
Sumário
18.1 Polimorfismo ad hoc (sobrecarga) . . . . . . . . . . . . . . . . . . . . . . . 18-1
18.2 Tipos qualificados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18-2
18.3 Classes e Instâncias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18-2
18.4 Tipo principal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18-3
18.5 Definição padrão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18-3
18.6 Exemplos de instâncias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18-4
18.7 Instâncias com restrições . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18-4
18.8 Derivação de instâncias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18-5
18.8.1 Herança . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18-5
18.9 Alguma classes do prelúdio . . . . . . . . . . . . . . . . . . . . . . . . . . . 18-5
18.9.1 A classe Show . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18-5
18.9.2 A classe Eq . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18-6
18.9.3 A classe Ord . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18-6
18.9.4 A classe Enum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18-7
18.9.5 A classe Num . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18-7
18.10Exercícios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18-8
18.11Soluções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18-11
• O operador (+) tem sido usado para somar tanto valores inteiros como valores fracionários.
• O operador (==) pode ser usado para comparar inteiros, caractres, listas de inteiros, strings, booleanos, ...
não serve, pois são tipos demasiado genéricos e fariam com que fossem aceitas expressões como
18-1
’a’ + ’b’
True + False
"está" + "errado"
div == mod
e estas expressões resultariam em erro, pois estas operações não estão definidas para trabalhar com valores destes
tipos.
Em Haskell esta situação é resolvida através de tipos qualificados (qualified types), fazendo uso da noção de
classe de tipos.
Uma classe pode ser vista como um conjunto de tipos. Por exemplo: Sendo Num uma classe (a classe dos tipos
numéricos) que tem como elementos os tipos:
e lê-se
para todo o tipo a que pertence à classe Num, (+) tem tipo a -> a -> a.
Desta forma uma classe surge como uma forma de classificar tipos quanto às funcionalidades a ele associadas.
Neste sentido as classes podem ser vistas como os tipos dos tipos.
Os tipos que pertencem a uma classe são chamados de instâncias da classe.
A capacidade de qualificar tipos polimórficos é uma característica inovadora de Haskell.
Todo tipo a da classe Num deve ter as operações (+) e (*) definidas. Para declarar Int e Float como elementos
da classe Num, tem que se fazer as seguintes declarações de instância:
18-2
instance Num Float where
(+) = primPlusFloat
(*) = primMulFloat
Neste caso as funções primPlusInt, primMulInt, primPlusFloat e primMulFloat são funções primitivas da
linguagem. Se x::Int e y::Int, então x + y ≡ primPlusInt x y. Se x::Float e y::Float, então x + y ≡
primPlusFloat x y.
sum [] = 0
sum (x:xs) = x + sum xs
sum :: [a] -> a seria um tipo demasiado geral. Porquê? Qual será o tipo principal da função product?
elem _ [] = False
elem x (y:ys) = (x == y) || elem x ys
A classe pre-definida Eq é formada pelos tipos para os quais existem operações de comparação de igualdade e
desigualdade:
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
-- Minimal complete difinition: (==) or (/=)
x == y = not (x /= y)
x /= y = not (x == y)
18-3
Esta classe introduz as funções (==) e (/=), e também fornece definições padrão para estes métodos, chamados
métodos default.
Caso a definição de uma função seja omitida numa declaração de instância, o sistema assume a definição padrão
feita na classe. Se existir uma nova definição do método na declaração de instância, esta definição será usada.
• O tipo PontoCor abaixo também pode ser declarado como instância da classe Eq:
• O tipo Nat também pode ser declarado como instância da classe Eq:
data BinTree a = V
| N a (BinTree a) (BinTree a)
• Duas árvores são iguais se tiverem a mesma estrutura (a mesma forma) e se os valores que estão nos nós
também forem iguais.
• Portanto, para fazer o teste de igualdade para o tipo BinTree a, necessariamente tem que se saber como
testar a igualdade entre os valores que estão nos nós.
• Só poderemos declarar BinTree a como instância da classe Eq se a também for uma instância de Eq.
18-4
• Este tipo de restrição pode ser colocado na declaração de instância.
• Nestes casos o compilador pode gerar sozinho a definição da função a partir da definição do tipo.
data BinTree a = V
| N a (BinTree a) (BinTree a)
deriving (Eq)
18.8.1 Herança
• O sistema de classes de Haskell também suporta a noção de herança, onde uma classe pode herdar todos
os métodos de uma outra classe, e ao mesmo tempo ter seus próprios métodos.
• Todo tipo que é instância de Ord tem que ser necessariamente instância de Eq.
• Haskell suporta herança múltipla: uma classe pode ter mais do que uma superclasse.
18-5
class Show a where
show :: a -> String
showsPrec :: Int -> a -> ShowS
showList :: [a] -> ShowS
• Exemplo:
18.9.2 A classe Eq
• Define igualdade (==) e desigualdade (/=).
• Eq pode ser derivada para qualquer tipo cujos constituintes são instâncias de Eq.
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
• Ord pode ser derivada para qualquer tipo cujos constituintes são instâncias de Ord. A ordenação dos valores
é determinada pela ordem dos construtores na declaração do tipo.
data Ordering = LT | EQ | GT
18-6
18.9.4 A classe Enum
• Define operações em tipos sequencialmante ordenados (enumerações).
• Enum pode ser derivada para qualquer tipo enumerado (os construtores de dados são todos constantes). Os
construtores são numerados da esquerda para a direita começando com 0.
• Definição completa mínima: toEnum e fromEnum.
• Um literal inteiro representa a aplicação da função fromInteger ao valor apropriado do tipo Integer. Por-
tanto o tipo destes literais é (Num a) => a.
• Exemplo: 35 é na verdade fromInteger 35
:t 35
35 :: Num a => a
35 :: Double ⇒ 35.0
35 :: Rational ⇒ 35 % 1
18-7
18.10 Exercícios
Tarefa 18.1
Em JavaScript e em algumas outras linguagens com tipagem dinâmica o tipo do teste em uma expressão
condicional pode ser de quase qualquer tipo. Por exemplo, em JavaScript, você pode escrever expressões
como
Embora em Haskell a limitação do tipo do teste em uma expressão condicional ao tipo Bool funcione
melhor, vamos implementar um comportamento semelhante ao encontrado no JavaScript, just for fun!
1. Vamos começar com uma declaração de classe de tipo. Defina uma classe YesNo contendo um
método chamado yesno que permita converter um valor de qualquer tipo que seja instância desta
classe para o tipo Bool.
Em ouras palavras, a classe YesNo deve introduzir uma função yesno que recebe um valor de um
tipo que seja instância da classe, resultando em um valor do tipo Bool. O argumento de yesno pode
ser interpretado como tendo algum conceito de veracidade, e a função yesno nos diz claramente se
ele é verdadeiro ou falso.
Lembre-se que na definição da classe enumeramos os seus métodos com as respectivas assinaturas
de tipo.
2. O próximo passo é definir algumas instâncias. Com certeza o tipo Bool pode ser uma instância da
classe YesNo. Faça a definição de instância da classe YesNo para o tipo Bool.
3. A lista vazia é considerada como falso, enquanto que listas não vazias são consideradas como ver-
dadeiro. Defina uma instância da classe YesNo para o tipo das listas [a].
4. Um valor do tipo Maybe a pode ser interpretado como um indicativo de sucesso ou falha de uma
computação, tendo um valor agregado do tipo a em caso de sucesso. Com certeza o tipo Maybe a
também pode ser uma instância da classe YesNo. Defina esta instância.
5. Para números vamos assumir que (como no JavaScript) qualquer número que não seja zero é verda-
deiro e zero é falso. Defina uma instância da classe YesNo para o Int.
6. Defina um tipo algébrico Semaforo para representar os possíveis estados de um semáforo de trânsito:
verde, amarelo e vermelho. Use os respectivos construtores de dados Verde, Amarelo e Vermelho.
Um sinal do semáforo também pode ser um valor yesno. Se ele for vermelho, o condutor deve parar.
Se for verde, o condutor pode seguir. E se for amarelo? Eh! Eu geralmente acelero e passo no
amarelo, porque gosto de viver com adrenalina!
Defina uma instância do tipo Semaforo para a classe YesNo.
7. Agora que temos algumas instâncias da classe YesNo, podemos brincar. Determine o valor das
seguintes expressões:
18-8
(a) yesno $ length []
(b) yesno "bom dia"
(c) yesno ""
(d) yesno (Just 12.4)
(e) yesno True
(f) yesno []
(g) yesno [("ana",10), ("pedro",12), ("beatriz", 9)]
(h) yesno Vermelho
Assim você pode confirmar que yesno é uma função sobrecarregada, com uma versão específica
para cada um dos tipos que são instâncias da classe YesNo.
8. Agora vamos fazer uma função chamada yesnoIf que imita a expressão if do Haskell, mas que
aceita expressões de qualquer tipo que seja instância da classe YesNo como sendo o teste. Defina a
função yesnoIf, indicando também o seu tipo.
A função yesnoIf recebe um valor YesNo e dois outros valores de um determinado tipo. Se o primeiro
valor corresponder ao conceito de yes, o resultado deve ser o primeiro dos outros dois valores; caso
contrário a função resulta no segundo dos outros dois valores.
Tarefa 18.2
18-9
Tarefa 18.3
5. Defina uma instância da classe Show para o tipo Nat. A string resultante da aplicação da função show
deve ser da form #i , onde i é o número escrito em notação decimal. Por exemplo:
show Zero #0
show (Succ Zero) #1
show (Succ (Succ Zero)) #2
show (Succ (Succ (Succ Zero))) #3
6. Defina uma instância da classe Read para o tipo Nat. O argumento da função read deverá ser uma
string da form #i , onde i é o número escrito em notação decimal. Por exemplo:
Tarefa 18.4
Considere o tipo
1. Defina uma função que verifica se uma árvore binária é vazia ou não.
2. Defina uma função que recebe um valor e uma árvore binária e insere o valor na árvore binária
mantendo-a a ordenada, resultando na nova árvore assim obtida.
3. Defina uma função que recebe um valor e uma árvore binária e verifica se o valor é um elemento da
árvore.
4. Modifique a definição do tipo para que sejam criadas automaticamente istâncias desse tipo para as
classes Read e Show.
18-10
18.11 Soluções
18-11
18-12