Escolar Documentos
Profissional Documentos
Cultura Documentos
LF Apostila Haskell PDF
LF Apostila Haskell PDF
USANDO HASKELL
Programao Funcional
Usando Haskell
UFPI/CCN/DIE
Teresina-Pi
Setembro de 2009
c
Copyright 2009, Departamento de Informtica e Estatstica,
Centro de Cincias da Natureza,
Universidade Federal do Piau.
Todos os direitos reservados.
c
Copyright 2009, Departamento de Informtica e Estatstica,
Centro de Cincias da Natureza,
Universidade Federal do Piau.
All rights reserved.
iii
Apresentao
E-mail: vieira.ufpi@gmail.com
iv
Contedo
Introduo ix
1 Programao Funcional 15
1.1 Introduo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.2 Computabilidade de funes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.3 Anlise de dependncias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.4 Funes e expresses aplicativas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.4.1 Independncia da ordem de avaliao . . . . . . . . . . . . . . . . . . . . . 19
1.4.2 Transparncia referencial . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.4.3 Interfaces manifestas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
1.5 Definio de funes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
1.5.1 Definies explcitas e implcitas de variveis . . . . . . . . . . . . . . . . 24
1.5.2 Definies explcitas e implcitas de funes . . . . . . . . . . . . . . . . . 25
1.5.3 Definies de funes por enumerao . . . . . . . . . . . . . . . . . . . . 27
1.5.4 Definio de funes por intencionalidade . . . . . . . . . . . . . . . . . . 27
1.5.5 Definio de funes por composio . . . . . . . . . . . . . . . . . . . . . 27
1.5.6 Definio de funes por casos . . . . . . . . . . . . . . . . . . . . . . . . 28
1.5.7 Definio de funes por recurso . . . . . . . . . . . . . . . . . . . . . . . 28
1.6 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2 -clculo 33
2.1 Introduo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.2 -expresses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.3 A sintaxe do -clculo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
2.3.1 Funes e constantes pr-definidas . . . . . . . . . . . . . . . . . . . . . . 35
2.3.2 -abstraes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.3.3 Aplicao de funo e currificao . . . . . . . . . . . . . . . . . . . . . . 36
2.4 A semntica operacional do -clculo . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.4.1 Ocorrncias livres ou ligadas . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.4.2 Combinadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
2.5 Regras de converso entre -expresses . . . . . . . . . . . . . . . . . . . . . . . . 41
v
2.5.1 -converso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
2.5.2 -converso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
2.5.3 -converso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
2.5.4 Convertibilidade de -expresses . . . . . . . . . . . . . . . . . . . . . . . 42
2.5.5 Captura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
2.5.6 Provando a conversibilidade . . . . . . . . . . . . . . . . . . . . . . . . . . 45
2.5.7 Uma nova notao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
2.6 Ordem de reduo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
2.7 Funes recursivas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
2.8 Algumas definies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
2.9 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4 O tipo Lista 83
4.1 Introdio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4.2 Funes sobre listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.3 Pattern matching revisado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
4.4 Compreenses e expresses ZF (Zermelo-Fraenkel) . . . . . . . . . . . . . . . . . 91
4.5 Funes de alta ordem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
vi
4.5.1 A funo map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
4.5.2 Funes annimas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
4.6 Polimorfismo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
4.6.1 Tipos variveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
4.6.2 O tipo mais geral . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
4.7 Induo estrutural . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
4.8 Composio de funes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
4.8.1 Composio avanada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
4.8.2 Esquema de provas usando composio . . . . . . . . . . . . . . . . . . . . 109
4.9 Aplicao parcial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
4.9.1 Seo de operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
4.10 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
vii
5.8 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
viii
B Compilao e execuo de programas em Haskell 205
B.1 Introduo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
B.2 Baixando e instalando o GHC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
B.3 Compilando em GHC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
B.3.1 Passando parmetros para um programa executvel . . . . . . . . . . . . . 206
B.3.2 Diretivas de compilao . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
ix
Introduo
Em 2004, Philip Wadler1 escreveu o artigo Why no one uses functional languages, onde ele
comenta sobre a pouca utilizao das linguagens funcionais na Indstria e ambientes comerciais
[50]. Para ele, dizer que ningum usa linguagem funcional, um exagero. As chamadas tele-
fnicas no Parlamento Europeu so roteadas por um programa escrito em Erlang, a linguagem
funcional da Ericsson. A rede Cornell distribui CDs virtuais usando o sistema Esemble, escrito
em CAML e a Polygram vende CDs na Europa utilizando Natural Expert, da Software AG. As
linguagens Erlang (www.erlang.se) e ML Works de Harlequin (www.harlequin.com) apresentam
um extensivo ambiente de suporte ao usurio. Alm disso, as linguagens funcionais so mais
adequadas construo de provadores de teoremas, incluindo o sistema HOL que foi utilizado
na depurao do projeto de multiprocessadores da linha HP 9000.
Ainda segundo Wadler, as linguagens funcionais produzem um cdigo de mquina com uma
melhoria de uma ordem de magnitude e, nem sempre, estes resultados so mostrados. Normal-
mente, se mostram fatores de 4. Mesmo assim, um cdigo que quatro vezes menor, quatro vezes
mais rpido de ser escrito ou quatro vezes mais fcil de ser mantido no pode ser jogado fora.
Para ele, os principais fatores que influenciam na escolha de uma linguagem de programao so:
1
grficas para as linguagens funcionais, muito esforo tem sido feito nesta direo, nos l-
timos tempos. Haskell tem Fudgets, Gadgets, Haggis, HOpenGL e Hugs Tk. SML/NJ
tem duas: eXene e SML Tk. Haskell e ML tm, ambas, um poderoso sistema de mdulos
que tornam suas bibliotecas fceis de serem construdas, j tendo a biblioteca Edison com
estruturas de dados eficientes, construda por Okasaki [28] e mantida por Robert Dock-
ins. Haskell ainda tem HSQL com interfaces para uma variedade de Bancos de Dados,
incluindo MySQL, Postgres, ODBC, SQLite e Oracle. Haskell ainda tem Happy, um ger-
ador de parsers LALR, similar ao yacc, atualmente extendido para produzir parsers LR
para gramticas ambguas.
Ferramentas. Uma linguagem para ser utilizvel necessita de ferramentas para depurao
e profiler. Estas ferramentas so fceis de serem construdas para linguagens estritas,
no entanto, so muito difceis de serem construdas para linguagens lazy, onde a ordem
de avaliao no conhecida a priori. Verifica-se uma exceo em Haskell, onde muitas
ferramentas de profiler j esto disponveis e muitas outras esto em pleno desenvolvimento.
2
pura, mas consegue imitar as atribuies das linguagens imperativas utilizando uma teoria
funcional complexa que a semntica de aes, implementadas atravs de mnadas2 .
Resumidamente, para ser bem utilizada, uma linguagem deve suportar trabalho interativo,
possuir bibliotecas extensas, ser altamente portvel, ter uma implementao estvel e fcil de ser
instalada, ter depuradores e profilers, ser acompanhada de cursos de treinamentos e j ter sido
utilizada, com sucesso, em uma boa quantidade de projetos.
Para o autor citado, todos estes requisitos j so perfeitamente atendidos por algumas lingua-
gens funcionais, por exemplo Haskell. Para ele, o que ainda existe um preconceito injustificvel
por parte de alguns programadores de outros paradigmas de programao, apesar de que este
quadro tem se modificado vertiginosamente ao longo dos ltimos anos.
Na realidade, Haskell oferece aos usurios trs caractersticas principais: novidade, poder e
prazer [29]:
Poder. Como um cdigo puro no tem envolvimento com o mundo externo, porque as
funes respondem apenas a entradas visveis, possvel se estabelecer propriedades sobre
seus comportamentos, que devem ser sempre verdadeiros, podendo, estes comportamen-
tos, serem testados de forma automtica. Na linguagem tambm so utilizadas tcnicas
tradicionais para se testar cdigos que devem interagir com arquivos, redes ou hardware
exticos. No entanto, este cdigo impuro muito menor do que seria encontrado em um
programa codificado em uma linguagem tradicional. Isso aumenta a nossa confiana de
que o cdigo seja slido.
Prazer. Acredita-se ser fcil entender a programao em Haskell e isto torna possvel
construir programas pequenos e em um curto espao de tempo. Para se programar em
Haskell, so importadas muitas idias da Matemtica abstrata para o campo prtico, o que
tornam esta programao prazeirosa.
2
A semntica de aes um tema tratado no Captulo 7. Os mnadas no so aqui tratados, uma vez que seu
estudo requer, como pr-requsito, o conhecimento aprofundado sobre implementao de linguagens funcionais.
3
Resumidamente, Haskell uma linguagem que representa uma novidade, onde o leitor con-
vidade a pensar em programao a partir de um ponto de vista diferente dos j experimentados,
representando uma nova perspectiva. Haskell uma linguagem poderosa, porque seus programas
so pequenos, rpidos e seguros. Finalmente, Haskell uma linguagem que proporciona prazer
em se programar com ela, porque em seus programas so aplicadas tcnicas de programao
adequadas para resolver problemas do mundo real.
Haskell na Indstria
Baseado em [29], sero mostradas a seguir alguns exemplos de grandes sistemas codificados em
Haskell:
Em termos de companhias que j utilizam Haskell em 2008, pode ser visto a partir da pgina
wiki (http://www.haskell.org/haskellwiki/Haskell_in_industry).
ABN AMRO. Um banco internacional que usa Haskell na avaliao de riscos e prottipos
de seus derivativos financeiros.
Amgen. Uma companhia de biotecnologia que cria modelos matemtidos e outras apli-
caes complexas.
A nosso ver, o que existe mesmo a falta de informao e conhecimento do que realmente
programao funcional e quais as suas vantagens e desvantagens em relao programao em
outros paradigmas. Esta Apostila tenta prover subsdios para que seus usurios possam iniciar
um processo de discusso sobre o tema. Para desencadear este processo, vamos comear fazendo
uma comparao entre a programao funcional e a programao estruturada.
At os anos 80, os programas de computadores eram pequenos porque os problemas que eles re-
solviam computacionamente tambm eram pequenos. Este tipo de programao ficou conhecido
como programming in the small. O aumento de desempenho verificado nos computadores pro-
porcionou que problemas bem maiores tambm pudessem ser solucionados por computador, o
que no era possvel ser realizado com as mquinas anteriores. Como consequncia, os programas
passaram a ser bem maiores que os anteriores, caracterizando um novo tipo de programao que
ficou conhecido como programming in the large.
4
Este novo tipo de programao trouxe, como conseqncia, a necessidade de novos mtodos de
construo de software. Por serem grandes, os programas passaram a ser construdos, no apenas
por uma nica pessoa, mas por grupos ou times de pessoas, possivelmente trabalhando em locais
distintos. Foi necessrio desenvolver novos mtodos de trabalho em grupo que proporcionassem
a construo segura e eficiente destes softwares. A palavra chave passou a ser produtividade.
Este movimento ficou conhecido como a crise do software dos anos 80 e a soluo encontrada
para resolv-lo ficou conhecida como programao estruturada.
Para analisar as similaridades existentes entre a programao estruturada e a programao
funcional, faz-se necessrio conhecer os fundamentos nos quais se baseiam estas duas formas
de se construir programas. Vamos iniciar relembrando os princpios nos quais se baseiam a
programao estruturada. Hoare enumerou seis princpios fundamentais da estruturao de
programas [25]:
3. Independncia das partes. Este princpio apregoa que os significados de duas partes,
no sobrepostas, podem ser entendidos de forma completamente independente, ou seja, E
pode ser entendida independentemente de F e vice versa. Isto acontece porque o resultado
computado por um operador depende apenas dos valores de suas entradas.
5
uma programao onde os programas nela escritos so mais tratveis matematicamente.
Apesar de todas as respostas acima serem corretas, no que se refere caracterizao deste
tipo de programao, elas no so conclusivas. Aludem ao que a programao estruturada no ,
mas no dizem o que realmente a programao estruturada. Na realidade, uma resposta
coerente para a pergunta sobre a programao estruturada pode ser: programas estruturados
so programas construdos de forma modular.
Esta uma resposta afirmativa e que atinge o cerne da questo. A construo de programas
modulares responsvel pela grande melhoria na construo de software, sendo a tcnica re-
sponsvel pelo notrio aumento de produtividade de software que ultimamente tem se verificado.
E por que a modularidade to determinante? A resposta imediata: na modularidade, os
problemas so decompostos em problemas menores e as solues para estes sub-problemas so
mais fceis de serem encontradas. No entanto, estas pequenas solues devem ser combinadas
para representar uma soluo para o problema original como um todo. Modula II, Ada, Pascal,
C, C++, Standard ML, Haskell, Java, Eiffel e todas as modernas linguagens de programao, in-
dependente do paradigma ao qual pertenam, foram projetadas ou adaptadas depois para serem
modulares.
O aumento de produtividade na programao estruturada se verifica porque:
Vamos agora conhecer a programao funcional para podermos estabelecer uma comparao
entre ela e a programao estruturada.
John Hughes [13], comenta uma situao semelhante quando se pergunta a algum sobre o que
programao funcional. Normalmente, se tem como resultado uma ou mais das seguintes
respostas:
uma linguagem onde pode-se substituir, a qualquer tempo, variveis por seus valores
(transparncia referencial) ou
6
Esta uma resposta afirmativa, precisando apenas de um complemento para que ela fique
completa. Este complemento se refere s caractersticas que as linguagens funcionais apresentam
e que so responsveis por esta melhoria na modularidade dos sistemas. Estas caractersticas
podem ser sumarizadas na seguinte observao: a programao funcional melhora a modulari-
dade, provendo mdulos menores, mais simples e mais gerais, atravs das funes de alta ordem
e lazy evaluation.
Estas duas caractersticas, verificadas apenas nas linguagens funcionais, que so responsveis
pela grande modularidade por elas proporcionada. Dessa forma, as linguagens funcionais so
altamente estruturadas e, portanto, representam uma soluo adequada para a to propalada
crise do software dos anos 80.
Para continuar esta viagem pelo mundo da programao funcional, tentando entender sua
fundamentao terica, necessrio conhecer tambm como as linguagens so implementadas.
Inicialmente, ser analisada a implementao de linguagens tradicionais e depois esta anlise se
particulariza para o caso da implementao das linguagens funcionais.
7
Problemas
Linguagemde
especificaoformal
Especificao
formal
Linguagemdeprogramao
dealtonvel
Programaem
linguagemdealtonvel
Biblioteca Compilador
Programaem
linguagemdemquina
Entradade
dados
Resultados
duas etapas [1]. Na primeira delas, feita uma traduo do programa escrito em linguagem de
alto nvel para um programa codificado em linguagem intermediria, chamada de linguagem de
montagem (Assembly). Na etapa seguinte, feita a traduo do programa em linguagem de
montagem para o programa executvel, em linguagem de mquina. Este processo est mostrado
na Figura 2.
Esta mesma metodologia foi tentada por alguns pesquisadores na traduo de programas cod-
ificados em linguagens funcionais para programas em linguagem de mquina, mas os resultados
no foram animadores [23]. Os cdigos executveis gerados eram todos de baixo desempenho.
Por este motivo, os pesquisadores da rea de implementao de linguagens funcionais se viram
obrigados a buscar outras alternativas de compilao para estas linguagens.
8
A implementao de linguagens funcionais
Mquinas abstratas
Uma tcnica que tem sido usada com sucesso na traduo de programas codificados em linguagens
funcionais para programas codificados em -clculo de alto desempenho tem sido a transformao
das expresses iniciais em linguagens funcionais em expresses equivalentes em -clculo. Estas
-expresses iniciais, possivelmente, no apresentem um desempenho muito bom, mas sero
transformadas em -expresses mais simples, at atingir uma forma normal, se ela existir. O
resultado desta tcnica foi um sistema que ficou conhecido na literatura como mquinas abstratas.
A primeira mquina abstrata foi desenvolvida por Peter Landin em 1964 [20], que ganhou o
alcunha de mquina SECD, devido ao seu nome (Stack, Environment, Code, Dump).
4
-clculo uma teoria de funes que ser vista no Captulo 2 desta Apostila.
9
A mquina SECD usa a pilha S para a avaliao das -expresses Codificadas, utilizando o
ambiente E.
Uma otimizao importante na mquina SECD foi transferir alguma parcela do tempo de
execuo para o tempo de compilao. No caso, isto foi feito transformando as expresses com
notao infixa para a notao polonesa reversa que adequada para ser executada em pilha,
melhorando o ambiente de execuo. Para diferenciar da mquina SECD, esta mquina foi
chamada de SECD2.
Em 1979, David Turner [47] desenvolveu um processo de avaliao de expresses em SASL
(uma linguagem funcional) usando o que ficou conhecida como a mquina de combinadores.
Ele utilizou os combinadores S, K e I do -clculo5 para representar expresses, em vez de
-expresses, utilizando para isto um dos combinadores acima citados, sem variveis ligadas [8].
Turner traduziu diretamente as expresses em SASL para combinadores, sem passar pelo estgio
intermedirio das -expresses. A mquina de reduo de Turner foi chamada de Mquina de
Reduo SK e utilizava a reduo em grafos como mtodo para sua implementao. Esta
mquina teve um impacto muito intenso na implementao de linguagens aplicativas, pelo ganho
em eficincia, uma vez que ela no utilizava o ambiente da mquina SECD, mas tirava partido
do compartilhamento que conseguia nos grafos de reduo.
Um outro pesquisador que se tornou famoso no desenvolvimento de mquinas abstratas foi
Johnsson, a partir de 1984, quando ele deu incio a uma srie de publicaes [16, 17, 18] sobre este
tema, culminando com sua Tese de Doutorado, em 1987 [19], onde ele descreve uma mquina ab-
strata baseada em supercombinadores que eram combinadores abstrados do programa de usurio
para algumas necessidades particulares. A mquina inventada por Johnsson ficou conhecida pela
Mquina G e se caracterizou por promover uma melhoria na granularidade dos programas, que
era muito fina na mquina de reduo de Turner. Uma otimizao importante desta mquina foi
a utilizao de uma segunda pilha na avaliao de expresses aritmticas ou outras expresses
estritas.
Vrias outras mquinas abstratas foram construdas com bons resultados. Entre elas podem
ser citadas a mquina GMC e a mquina , idealizadas por Rafael Lins, da Universidade Federal
de Pernambuco [40].
Alm destas, uma que tem se destacado com excelentes resultados a mquina CMC,
tambm de Rafael [22, 40], onde um programa codificado em SASL traduzido para um programa
em Ansi C. Este processo est mostrado na Figura 4.
A escolha da linguagem C se deve ao fato de que os compiladores de C geram cdigos recon-
hecidamente portteis e eficientes. A mquina CMC baseada nos combinadores categricos
que se fundamentam na Teoria das Categorias Cartesianas Fechadas, recentemente utilizada em
diversas reas da Computao, sendo hoje um tema padro do discurso, nos grandes encontros
e eventos na rea da Informtica [41].
5
Combinadores e supercombinadores so temas do -clculo, objeto de estudo do Captulo 2, deste trabalho.
10
A comunidade de Haskell
Existem muitas formas pelas quais leitores e usurios podem entrar em contacto com outros
programadores usurios de Haskell, a fim de tirar dvidas, saber o que outras pessoas esto
falando sobre o tema ou at mesmo apenas para manter contacto social. Podem ser citadas:
Resumo
Este Captulo introdutrio foi feito na tentativa de apresentar a Programao Funcional como
uma alternativa importante na escolha de uma linguagem de programao, enumerando suas
principais caractersticas, mostrando suas principais vantagens sobre as outras. As vantagens
da programao sem atribuies em relao programao com atribuies so similares s da
programao sem gotos em relao programao com gotos. Resumidamente, so elas:
11
6. A programao funcional est fortemente acoplada Teoria da Computao.
Esta Apostila composta desta Introduo, 8 (oito) Captulos, as referncias bibliogrficas con-
sultadas e dois Apndices. Nesta Introduo, analisada a importncia das linguagens funcionais
e a necessidade de estudar o -clculo, justificando sua escolha como linguagem intermediria
entre as linguagens funcionais e o cdigo executvel. Alm disso, mostra-se porque as lingua-
gens funcionais aumentam a modularidade dos sistemas atravs das funes de alto nvel e do
mecanismo de avaliao preguiosa.
O Captulo 1 dedicado fundamentao das linguagens funcionais, abordando as princi-
pais diferenas entre elas e as linguagens de outros paradigmas. O mundo das linguagens de
programao dividido entre o mundo das expresses e o mundo das atribuies, evidenciando
as vantagens do primeiro mundo em relao ao segundo.
No Captulo 2, introduzido o -clculo, sua evoluo histrica e como ele usado nos dias
atuais. A teoria colocada de maneira simples e introdutria, dado o objetivo da Apostila.
No Captulo 3, inicia-se a programao em Haskell. So mostrados seus construtores e uma
srie de exemplos, analisando como as funes podem ser construdas em Haskell. So mostrados
os tipos de dados primitivos adotados em Haskell e os tipos estruturados mais simples que so as
tuplas. No Captulo, tambm so mostrados os esquemas de provas de programas, juntamente
com vrios exerccios, resolvidos ou propostos.
O Captulo 4 dedicado listas em Haskell. Este Captulo se torna necessrio, dada a im-
portncia que este tipo de dado tem nas linguagens funcionais. Neste Captulo, so mostradas
as compreenses ou expresses ZF e tambm mostrada a composio de funes como uma
caracterstica apenas das linguagens funcionais, usada na construo de funes. Um tema im-
portante e que discutido neste Captulo se refere s formas de provas da corretude de programas
em Haskell, usando induo estrutural sobre listas. No Captulo so mostrados vrios exemplos
resolvidos e, ao final, so colocados vrios exerccios apreciao do leitor.
O Captulo 5 dedicado aos tipos de dados algbricos. Inicialmente, so mostradas as type
class como formas de incluir um determinado tipo de dados construdo pelo usurio em em
uma classe de tipos que tenham funes em comum, dando origem sobrecarga como forma de
polimorfismo. Neste captulo so mostradas algumas estruturas de dados importantes que podem
ser construdas usando os tipos algbricos, a exemplo das especificaes de expresses em BNF e
das rvores. O Captulo apresenta um estudo sobre o tratamento de excees e como ele feito
em Haskell. Alm disso, o captulo apresenta um estudo sobre o mecanismo de avaliao lazy
que Haskell utiliza, possibilitando a construo de listas potencialmente infinitas. O Captulo
termina com a apresentao de formas de provas de programas em Haskell que utilizam tipos
algbricos.
O Captulo 6 dedicado ao estudo da modularizao de programas codificados em Haskell. O
Captulo iniciado com o estudo dos mdulos e como eles so construdos em Haskell, juntamente
com os mecanismos de importao e exportao destas entidades. No Captulo, so mostrados
como estes mdulos so compilados separadamente e como eles so linkados para formarem um
cdigo executvel. As diversas ferramentas de execuo de cdigo executvel que o sistema dispe
para a depurao de cdigo.
12
O Captulo 7 dedicado aos tipos de dados abstratos. Ele tem incio com um estudo simpli-
ficado sobre a eficincia em programas funcionais. Em seguida, so mostrados os tipos de dados
abstratos e as formas como eles so construdos em Haskell. Todos os tipos abstratos mostra-
dos so analisados quanto eficincia, tentando formas alternativas de constru-los levando em
considerao este fator. O Captulo termina, como todos os outros com um Resumo de seu
contedo.
O Captulo 8 dedicado s operaes de entrada e sada em Haskell, evidenciando o uso
de arquivos ou dispositivos como sada ou entrada de dados. Este processo em Haskell feito
atravs do mecanismo de aes, cuja semntica representa o contedo principal do Captulo.
A Apostila contempla as referncias bibliogrficas consultadas durante s sua elaborao jun-
tamente com algumas outras indicaes que devem ser analisadas por quem deseja conhecer
melhor o paradigma funcional.
O Apndice A dedicado s principais funes j constantes do arquivo Prelude.hs e que
podem ajudar o usurio na construo de novas funes.
A Apostila termina com o Apndice B, dedicado compilao de programas codificados em
Haskell, utilizando o compilador GHC (Glasgow Haskell Compiler ) que gera cdigo nativo. Ele
se tornou necessrio na tentativa de tornar a Apostila autocontida, proporcionando ao leitor que
ele codifique e gere programas em Haskell e os compile para ddigo executvel.
13
14
Captulo 1
Programao Funcional
1.1 Introduo
A programao funcional teve incio antes da inveno dos computadores eletrnicos. No incio
do sculo XX, muitos matemticos estavam preocupados com a fundamentao matemtica; em
particular, queriam saber mais sobre os conjuntos infinitos. Muito desta preocupao aconteceu
por causa do surgimento, no final do sculo XIX, de uma teoria que afirmava a existncia de vrias
ordens de infinitos, desenvolvida por George Cantor (1845-1918) [25]. Muitos matemticos, como
Leopold Kronecker (1823-1891), questionaram a existncia destes objetos e condenaram a teoria
de Cantor como pura enrolao. Estes matemticos defendiam que um objeto matemtico s
poderia existir se, pelo menos em princpio, pudesse ser construdo. Por este motivo, eles ficaram
conhecidos como construtivistas.
Mas o que significa dizer que um nmero, ou outro objeto matemtico, seja construtvel ?
Esta idia foi desenvolvida lentamente, ao longo de muitos anos. Guiseppe Peano (1858-1932),
um matemtico, lgico e lingista, escreveu Formulaire de Mathmatique (1894-1908), onde
mostrou como os nmeros naturais poderiam ser construdos atravs de finitas aplicaes da
funo sucessor. Comeando em 1923, Thoralf Skolen (1887-1963) mostrou que quase toda
a teoria dos nmeros naturais poderia ser desenvolvida construtivamente pelo uso intensivo de
definies recursivas, como as de Peano. Para evitar apelos questionveis sobre o infinito, pareceu
razovel chamar um objeto de construtvel se ele pudesse ser construdo em um nmero finito de
passos, cada um deles requerendo apenas uma quantidade finita de esforo. Assim, nas primeiras
dcadas do sculo XX, j existia considervel experincia sobre as definies recursivas de funes
sobre os nmeros naturais.
A cardinalidade (quantidade de elementos) dos conjuntos finitos era fcil ser conhecida, uma
vez que era necessrio apenas contar seus elementos. Mas, para os conjuntos infinitos, esta
tcnica no podia ser aplicada. Inicialmente, era necessrio definir o que era realmente um
conjunto infinito. Foi definido que um conjunto era infinito se fosse possvel construir uma
correspondncia biunvoca entre ele e um subconjunto prprio de si mesmo. Foram definidos
os conjuntos infinitos enumerveis, caracterizados pelos conjuntos infinitos para os quais fosse
15
possvel construir uma correspondncia biunvoca com o conjunto dos nmeros naturais, N.
Assim, todos os conjuntos infinitos enumerveis tinham a mesma cardinalidade, que foi definida
por 0 1 . Assim, as cardinalidades do conjunto dos nmeros inteiros, Z, e dos nmeros racionais,
Q, tambm so 0 , uma vez que possvel construir uma correspondncia biunvoca entre Z e N
e entre Q e N. Os conjuntos infinitos em que no fosse possvel estabelecer uma correspondncia
biunvoca entre eles e N, foram chamados de infinitos no enumerveis.
Dado um conjunto A, o conjunto das partes de A, denotado por P(A), o conjunto cujos
elementos so todos os subconjuntos de A, incluindo ele prprio e o conjunto vazio. Um resultado
que garante a existncia de cardinais infinitos o fato de que o conjunto das partes de um conjunto
tem sempre cardinalidade maior que a deste. Este fato ficou conhecido como Teorema de Cantor,
aqui descrito sem demonstrao.
Teorema de Cantor. Seja A um conjunto e P(A) o conjunto das partes de A. Ento a
cardinalidade de A, denotada por #A, menor que a cardinalidade de P(A), ou seja, #A <
#P(A).
Prova-se que o conjunto das partes de N equipotente (tem a mesma cardinalidade) ao
conjunto dos reais, R. Considerando que 2k representa o cardinal do conjunto das partes de
conjuntos com cardinalidades k, ento 20 a cardinalidade do conjunto dos nmeros reais, R.
Mas, o que isto tem a ver com a Computao? Na realidade, prova-se que existem 0
programas que podem ser definidos em uma linguagem de programao como C, Java, Haskell,
ou outra. Alm disso, existem 20 funes de N para N. Logo, conclui-se que existem infinitas
funes que no podem ser representadas algoritmicamente, ou seja, que no so computveis.
Isto significa que a quantidade de funes computveis muito menor que a quantidade de
funes no computveis. Este um resultado, no mnimo, inusitado.
16
McCarthy sentiu-se atrado a usar funes recursivas e, alm disso, ele tambm achou conveniente
passar funes como argumentos para outras funes. McCarthy verificou que o -clculo provia
uma notao conveniente para estes propsitos e, por isto, resolveu usar a notao de Church
em sua pesquisa. Ainda em 1958, no MIT, foi iniciado um projeto com o objetivo de construir
uma linguagem de programao que incorporasse estas idias. O resultado ficou conhecido como
LISP 1, que foi descrita por McCarthy, em 1960, em seu artigo Recursive Functions of Symbolic
Expressions and Their Computation by Machine [25]. Neste artigo, ele mostrou como vrios
programas complexos podiam ser expressos por funes puras operando sobre estruturas de lis-
tas. Este fato caracterizado, por alguns pesquisadores, como o marco inicial da programao
funcional.
No final da dcada de 1960 e incio da dcada de 1970, um grande nmero de cientistas
da Computao comearam a investigar a programao com funes puras, chamada de pro-
gramao aplicativa, uma vez que a operao central consistia na aplicao de uma funo a
seu argumento. Em particular, Peter Landin (1964, 1965 e 1966) desenvolveu muitas das idias
centrais para o uso, notao e implementao das linguagens de programao aplicativas, sendo
importante destacar sua tentativa de traduzir a definio de Algol 60, uma linguagem no fun-
cional, para o -clculo. Baseados neste estudo, Strachey e Scott construiram um mtodo de
definio da semntica de linguagens de programao, conhecido como semntica denotacional.
Em essncia, a semntica denotacional define o significado de um programa, em termos de um
programa funcional equivalente.
No entanto, a programao aplicativa, que havia sido investigada por um reduzido nmero
de pesquisadores nos anos de 1960 e 1970, passou a receber uma ateno bem maior aps 1978,
quando John Backus, o principal criador do FORTRAN, publicou um paper onde fez severas
crticas s linguagens de programao convencionais, sugerindo a criao de um novo paradigma
de programao. Ele props o paradigma chamado de programao funcional que, em essncia,
a programao aplicativa com nfase no uso de funcionais, que so funes que operam sobre
outras funes. Muitos dos funcionais de Backus foram inspirados em APL, uma linguagem
imperativa projetada na dcada de 1960, que provia operadores poderosos, sem atribuio, sobre
estruturas de dados. A partir desta publicao, o nmero de pesquisadores na rea de linguagens
de programao funcional tem aumentado significativamente.
17
Analisemos a expresso z = (2 a y + b) (2 a y + c). As duas sub-expresses entre
parnteses do lado direito do sinal de atribuio contm uma sub-expresso em comum (2 a y)
e qualquer compilador, com um mnimo de otimizao, transformaria este fragmento de cdigo,
da seguinte forma:
t = 2 a y;
z = (t + b) (t + c);
No mundo das expresses, seguro promover esta otimizao porque qualquer sub-expresso,
como 2 a y, tem sempre o mesmo valor, em um mesmo contexto. Analisemos agora, uma
situao similar no mundo das atribuies.
Sejam as expresses
y = 2 a y + b; e z = 2 a y + c;
onde tambm verificamos uma sub-expresso em comum (2 a y). Se for realizada a mesma
fatorao anterior, teremos:
t = 2 a y;
y = t + b; z = t + c;
Esta otimizao altera o valor da varivel z, porque os valores de y so diferentes nas duas
ocorrncias da sub-expresso 2 a y. Portanto, no possvel realizar esta otimizao. Apesar
da anlise de dependncia entre sub-expresses poder ser feita por um compilador, ela requer
tcnicas sofisticadas de anlise de fluxo. Tais anlises, normalmente so caras para serem real-
izadas e difceis de serem implementadas corretamente. No mundo das expresses, no existem
atualizaes destrutivas de variveis e, portanto, a varivel t no poderia ter mais de um valor.
De forma resumida, fcil e seguro realizar estas otimizaes nas expresses e difcil e inse-
guro de serem feitas no mundo das atribuies. Fica clara a vantagem das expresses sobre as
atribuies. O propsito da programao funcional extender as vantagens das expresses para
as linguagens de programao.
Vamos continuar analisando o mundo das expresses. Elas so estruturalmente simples porque
so compostas de aplicaes de operaes aritmticas a seus argumentos. Estas operaes so
funes puras, ou seja, so mapeamentos matemticos de entradas para sadas. Isto significa
que o resultado de uma operao depende apenas de suas entradas. Alm disso, uma expresso
construda a partir de funes puras e constantes tem sempre o mesmo valor. Por exemplo, seja
a funo pura f definida da seguinte forma:
f (u) = (u + b)(u + c)
18
f (3ax) = (3ax + b) (3ax + c) = (3 2 x + b) (3ax + c) = (6 x + b) (3ax + c) = . . . = 336
No campo das linguagens de programao, estas duas seqncias de avaliaes correspondem
aos mtodos de passagem de parmetros por valor e por nome, respectivamente. No primeiro caso,
a avaliao dita estrita" e, no segundo, ela dita no estrita", em relao a seu argumento x. A
passagem de parmetros por valor, normalmente, mais eficiente porque o argumento avaliado
apenas uma vez. No entanto, o que mais importante o fato de que o valor da expresso o
mesmo, independente da ordem de avaliao adotada. Apesar da ordem de avaliao escolhida
no ter influncia sobre o valor final da expresso, ela pode influenciar sobre o trmino, ou no,
do processo de avaliao. Por exemplo, a avaliao da funo f (x) = 1 para x = 1/a, em
um contexto em que a seja igual a zero, tem diferena nas duas ordens de avaliao. Se 1/a for
avaliado antes da substituio, a avaliao ser indefinida e no termina. Se for deixada para ser
feita depois da substituio, o resultado ser 1.
A programao aplicativa ou funcional freqentemente distingida da programao imper-
ativa que adota um estilo que faz uso de imperativos ou ordens, por exemplo, troque isto!,
v para tal lugar!, substitua isto!, e assim por diante. Ao contrrio, o mundo das expresses
envolve a descrio de valores. Por este motivo, o termo programao orientada por valores.
Na programao aplicativa, os programas tomam a forma de expresses aplicativas. Uma
expresso deste tipo uma constante (2, , e, etc) ou composta totalmente de aplicaes de
funes puras a seus argumentos, que tambm so expresses aplicativas. Em BNF, as expresses
aplicativas so definidas da seguinte forma:
A estrutura aplicativa se torna mais clara se as expresses forem escritas na forma pr-fixa,
ou seja, sum(prod (prod (2, a), x), b) em vez da forma usual, infixa, 2ax+b.
A programao aplicativa tem um nico construtor sinttico que a aplicao de uma funo a
seu argumento. Na realidade, este construtor to importante que, normalmente, representado
de forma implcita, por justaposio, em vez de explicitamente, atravs de algum smbolo. Desta
forma, sen x significa a aplicao da funo sen ao argumento x.
Pelo que foi visto na seo anterior, para entender as vantagens do mundo das expresses e as
fontes de suas propriedades, necessrio investigar a ordem de avaliao nas expresses arit-
mticas. Avaliar alguma coisa significa encontrar seu valor. Assim, podemos avaliar a expresso
aritmtica 5 4 + 3"encontrando seu valor que, no caso, 23.
Podemos tambm avaliar a expresso (3ax + b)(3ax + c)? A resposta no; a menos que
sejam conhecidos os valores de a, b, c e x. Para entender como isto feito, vamos mostrar como
uma expresso avaliada, atravs da construo de uma rvore de avaliao, conforme pode ser
visto na Figura 1.1, onde as variveis ficam nas folhas e as operaes nos ns internos.
O valor desta expresso depende do contexto em que ela avaliada. Por exemplo, vamos
avali-la em um contexto em que a = 2, b = 3, c = 2 e x = 3. A avaliao pode ser iniciada em
vrios pontos mas, por motivo de regularidade, ela ser feita da esquerda para a direita. Nesta
estrutura, cada operao em um n interno depende apenas dos resultados das operaes dos
ns abaixo dele, na rvore. A avaliao de uma sub-rvore afeta apenas a rvore acima dela,
no influenciando os resultados das sub-rvores que estejam a sua esquerda ou a sua direita.
Colocando todos os operadores de forma explcita, a expresso se transforma em:
19
Figura 1.1: rvore representativa da expresso (3ax+b)(3ax+c).
(3 a x + b) (3 a x + c)
Para realizar a primeira operao (a multiplicao 3 a) necesrio saber o valor de a que,
neste contexto, 2. Substituindo este valor na expresso, ela se torna
(3 2 x + b) (3 2 x + c)
Observe que o a da segunda sub-expresso tambm foi substitudo por causa do grafo uti-
lizado. O processo de avaliao continua, substituindo a expresso inicial por uma nova expresso.
Neste caso, por
(6 x + b (6 x + c)
A seqncia completa de todos os passos realizados no processo de avaliao mostrada a
seguir, onde a flexa dupla () significa a transformao de expresses.
(3 a x + b) (3 a x + c)
(3 2 x + b) (3 2 x + c)
(6 x + b) (6 x + c)
(6 3 + b) (6 3 + c)
(18 + b) (18 + c)
(18 + 3) (18 + c)
21 (18 + c)
21 (18 + (2))
21 16
336
Observe que se a avaliao tivesse sido iniciada pela sub-rvore direita da rvore inicial, o
resultado seria o mesmo, ou seja, qualquer ordem de avaliao produziria o resultado 336. Isto
ocorre porque, na avaliao de uma expresso pura3 , a avaliao de uma sub-expresso no afeta
o valor de qualquer outra sub-expresso porque no existe qualquer dependncia entre elas.
20
em qualquer ordem, sob demanda, podendo ser avaliados em paralelo. Cada operao depende
apenas de suas entradas que so os valores dos ns filhos. Este processo de avaliao pode ser
visto na Figura 1.2.
Este processo conhecido como decorao, onde cada n interno decorado com o valor
da aplicao da operao aos valores dos ns abaixo dele. A avaliao termina quando a raiz
da rvore for decorada com o valor final da expresso. Como afirmado anteriormente, diversos
processos podem acontecer em paralelo, na decorao da rvore, desde que seja observada a
estrutura de rvore. Isto significa que sempre se chega ao mesmo valor.
Esta propriedade verificada nas expresses puras, ou seja, a independncia da ordem de avali-
ao, chamada de propriedade de Church-Rosser. Ela permite a construo de compiladores
capazes de escolher a ordem de avaliao que faa o melhor uso dos recursos da mquina. A
possibilidade da avaliao ser realizada em paralelo, implica na utilizao de multiprocessadores
de forma bastante natural.
Por outro lado, as expresses impuras, normalmente, no apresentam esta propriedade,
conforme pode ser verificado no exemplo a seguir, em C. Seja a expresso a+2*fun(b). Ela
pura ou impura? Para responder a isso, devemos verificar a definio de fun. Por exemplo,
int fun(int x)
{
return(x * x);
}
int fun1(int x)
{
a = a + 1;
return (x * x);
}
Neste caso, fun1 uma pseudo-funo, porque ela no uma funo pura. Supondo que
a varivel a mencionada em fun1 seja a mesma varivel da expresso a+2*fun1(b), como
fun1 altera o valor de a, o valor de a+2*fun1(b) depende de qual operando do operador +
avaliado em primeiro lugar. Se, por exemplo, o valor de a for zero, caso ele seja avaliado primeiro
na expresso a + 2*fun1(b), o valor final da expresso ser 2b2 , ao passo que se 2*fun1(b)
for avaliado primeiro, a expresso final ter o valor 2b2 + 1.
Desta forma, para uma mesma linguagem, C, foram mostrados dois exemplos em que, no
primeiro, observa-se a independncia na ordem de avaliao e, no segundo, esta propriedade no
verificada.
Vamos novamente considerar o contexto de avaliao da seo anterior. Se uma pessoa fosse
avaliar manualmente a expresso (3ax + b)(3ax + c) jamais iria avaliar a sub-expresso 3ax,
que neste contexto 18, duas vezes. Uma vez avaliada esta sub-expresso, o avaliador humano
substituiria a sub-expresso 3ax por 18, em todos os casos onde ela aparecesse. A avaliao seria
feita da seguinte forma:
21
t=3ax
=32x
=6x
=63
= 18
(18 + b) (18 + c)
(18 + 3) (18 + c)
21 (18 + (2))
21 16
336
Isto acontece porque a avaliao de uma mesma expresso, em um contexto fixo sempre dar
como resultado o mesmo valor. Para os valores de a = 2 e x = 3, 3ax ser sempre igual a 18.
Esta tcnica de avaliao humana tambm utilizada na avaliao de expresses puras. Seu
desenvolvimento pode ser entendido observando a rvore de avaliao da expresso mostrada na
Figura 1.3. Como a sub-expresso 3ax ocorre duas vezes, no existe razo para se duplicar a
representao na rvore. O valor desta sub-expresso compartilhado pelas operaes dos ns
que se encontram acima do n desta operao.
* =336
+ =21 + =16
b=3 c=2
* =18
x=3
* =6
=3 a=2
A rigor, no se tem mais uma estrutura de rvore, e sim um grafo acclico. No entanto,
pode-se decorar o grafo partindo das folhas, da mesma maneira feita antes.
Esta propriedade chamada de transparncia referencial, e significa que, em um contexto
fixo, a substituio de sub-expresses por seus valores completamente independente da ex-
presso envolvente. Portanto, uma vez que uma expresso tenha sido avaliada em um dado
contexto, no mais necessrio avali-la, porque seu valor jamais ser alterado. De forma mais
geral, a transparncia referencial pode ser definida como a habilidade universal de substituir
iguais por iguais". Em um contexto em que a = 2 e x = 3, sempre pode-se substituir 3ax por 18
ou 18 por 3ax, sem que o valor da expresso envolvente seja alterado. A transparncia referencial
resulta do fato de que os operadores aritmticos no tm memria e, assim sendo, toda chamada
a um operador com as mesmas entradas produz sempre o mesmo resultado.
Mas por que a transparncia referencial importante? Da Matemtica, sabemos da im-
portncia de poder substituir iguais por iguais. Isto conduz derivao de novas equaes, a
partir de equaes dadas e a transformao de expresses em formas mais usuais e adequadas
para a prova de propriedades sobre elas.
No contexto das linguagens de programao, a transparncia referencial permite otimizaes
como a eliminao de sub-expresses comuns. Por exemplo, dada a definio da pseudo-funo
22
fun1, da seo anterior, claro que, como fun1 deixa em a o registro do nmero de vezes que
ela chamada, no se poderia eliminar a sub-expresso comum, fun1(b), da expresso
(a+2*fun1(b))*(c+2*fun1(b))
Isto acontece porque a troca do nmero de vezes que fun1 chamada altera o resultado da
expresso. Em algumas linguagens de programao, isto complica a eliminao de sub-expresses
comuns.
A evoluo da notao matemtica ao longo dos anos tem permitido que ela seja utilizada,
com sucesso, para exibir muitas propriedades. Uma destas propriedades se refere s interfaces
manifestas, ou seja, as conexes de entradas e sadas entre uma sub-expresso e a expresso que
a envolve so visualmente bvias. Consideremos a expresso 4 + 5. O resultado desta adio
depende apenas das entradas para a operao (4 e 5) e elas esto mostradas de forma clara
na expresso, ou seja, uma est colocada esquerda e a outra direita do operador +. No
existem entradas escondidas para este operador.
Vamos imaginar agora a pseudo-funo fun2, definida da seguinte forma:
int fun2(int x)
{
a = a + 1;
return (a * x);
}
No existe uma forma de se conhecer o valor de fun2(5) sem antes saber o valor da varivel
no local, a. O valor de a atualizado em cada chamada a fun2, ento fun2(5) tem valores
diferentes em cada chamada. Esta situao caracteriza a existncia de interfaces escondidas para
a funo fun2, tornando difcil, ou mesmo impossvel, se prever o comportamento da funo.
Consideremos, novamente, a expresso (2ax + b) (2ax + c). As entradas para o primeiro
operador + (2ax e b) esto manifestas. O papel da sub-expresso 2ax + b, dentro da expresso
completa, tambm manifesto, ou seja, ela representa o argumento esquerdo do operador de
multiplicao. No existem sadas escondidas ou side effects na adio. As entradas so as
sub-expresses 2ax e b, em qualquer lado do operador e a sada facilmente calculada e liberada
para a expresso envolvente.
As caractersticas das interfaces manifestas podem ser resumidas da seguinte forma: as ex-
presses podem ser representadas por rvores e a mesma rvore representa tanto a estrutura
sinttica de uma expresso quanto a forma como os dados fluem na expresso. As sub-expresses
que se comunicam entre si podem sempre ser colocadas em posies contguas na rvore ou na
forma escrita da expresso. Esta caracterstica no vlida no mundo das atribuies porque
as variveis alterveis permitem comunicao no local. Em geral, o grfico do fluxo de dados
no uma rvore e pode ser uma estrutura muito diferente da rvore sinttica. Assim, pode ser
impossvel colocar juntas as partes comunicantes, para que suas interfaces sejam bvias.
23
codificadas em Haskell. Do ponto de vista matemtico, as funes so definidas sobre conjuntos,
domnio e imagem, sem qualquer preocupao como elas so executadas para encontrar um
resultado. J no mundo da computao, as funes so declaradas sobre tipos e levam-se em
conta os algoritmos utilizados para implement-las e a diferena de desempenho entre eles tem
importncia fundamental. Apesar desta diferena de pontos de vista, o processo de passagem de
um mundo para o outro quase um processo de traduo direta. Nas sees seguintes, as funes
sero definidas levando-se em conta o ponto de vista da Matemtica e deixamos as definies do
ponto de vista computacional para serem feitas a partir do Captulo 3.
Vamos considerar inicialmente as definies explcitas de variveis. Uma definio de uma varivel
explcita, em uma equao, se ela aparece no lado esquerdo desta equao e no aparece no
seu lado direito. Por exemplo, a equao
y = 2ax
define explicitamente a varivel y. As definies explcitas tm a vantagem de poderem ser inter-
pretadas como regras de reescritas que informam como substituir diretamente uma expresso por
outra. Por exemplo, a definio anterior de y implica na regra de reescrita y 2ax informando
como eliminar a varivel y em uma frmula, onde y ocorra. Por exemplo, para eliminar y da
expresso 3y 2 + 5y + 1 aplica-se a regra de reescrita acima, para se obter
3y 2 + 5y + 1 3(2ax)2 + 5(2ax) + 1 12a2 x2 + 10ax
em que a varivel y no ocorre na expresso final. A definio explcita de variveis pode
ser extendida para conjuntos de equaes simultneas. Um conjunto de variveis definido
explicitamente por um conjunto de equaes, se
as equaes puderem ser ordenadas, de forma que nenhuma delas use em seu lado direito
uma varivel j definida anteriormente na lista.
Estas regras podem ser aplicadas na seguinte ordem: a primeira delas aplicada at que no
exista mais y, em seguida a segunda aplicada at que no exista mais x e, finalmente, a terceira
aplicada at que no exista mais a. Desta forma teremos a seguinte seqncia de redues:
y 2 a x 2 a 2 2 3 2 6 2 12
Por outro lado, uma varivel definida implicitamente em uma equao se ela aparecer nos
dois lados desta equao. Por exemplo, a equao
24
2a = a + 3
define implicitamente a como 3. Para encontrar o valor de a necessrio resolver a equao
usando tcnicas da lgebra. O processo de soluo pode ser visto como uma forma de converter
uma definio implcita em uma definio explcita, mais usual, uma vez que uma definio
implcita no pode ser convertida diretamente em uma regra de reescrita. A equao anterior,
2a = a + 3, no informa explicitamente o que deve substituir a na expresso 2ax, por exemplo.
Para encontrar este valor necessrio utilizar as regras da lgebra, que podem no ser triviais.
Alm disso, as regras de reescrita que resultam de definies explcitas sempre terminam, ou seja,
a aplicao repetida das regras de reescritas elimina todas as ocorrncias da varivel definida.
No entanto, possvel escrever definies implcitas que no terminam, ou seja, nada definem.
Considere, como exemplo, a definio implcita
a=a+1
Apesar de sabermos que esta equao no tem soluo, este fato pode no ser to bvio, em
casos mais complexos. Se, ingenuamente, interpretarmos esta equao como a regra de reescrita
aa+1
ento chegaremos a um no determinismo na seguinte seqncia de redues:
2a 2(a + 1) 2((a + 1) + 1) . . .
As variveis tambm podem ser definidas implicitamente por conjuntos de equaes si-
multneas. Por exemplo, o conjunto de equaes
(
2a = a + 3
d 1 = 3d + a
define implicitamente a = 3 e d = 2. Podemos tambm ter definies implcitas em que as
variveis no aparecem nos dois lados da mesma equao. Por exemplo, o conjunto de equaes
(
2a = x
x+1=a+4
em que, nem a nem x aparecem nos dois lados de uma mesma equao, define implicitamente
a e x. Neste caso, no existe qualquer forma de ordenao destas equaes de maneira que
as ltimas equaes no faam uso de variveis j definidas nas equaes anteriores. A forma
implcita pode ser observada pela transformao das duas equaes em uma s, ou seja,
2a + 1 = a + 4
Em resumo, uma definio explcita de uma varivel informa o valor desta varivel, enquanto
uma definio implcita estabelece propriedades que apenas esta varivel deve apresentar. A
determinao do valor de uma varivel definida implicitamente exige um processo de soluo.
Aps termos analisado as definies explcitas e implcitas das variveis, vamos agora considerar
como elas so aplicadas ao caso das funes. Por exemplo, as duas equaes, a seguir, definem
implicitamente a funo implica.
and [p, implica (p, q)] = and (p, q)
and [not p, implica (p, q)] = or [not p, and (not p, q)]
Estas equaes no podem ser usadas explicitamente para avaliar uma expresso como
implica (T rue, F alse). Usando teoremas da lgebra booleana, estas equaes podem ser re-
solvidas para se chegar seguinte definio explcita:
25
implica (p, q) = or (not p, q)
A definio explcita permite que implica (T rue, F alse) seja avaliada usando substituio.
Uma vantagem da programao funcional que ela simplifica a transformao de uma definio
implcita em uma definio explcita. Isto muito importante porque as especificaes formais
de sistemas de softwares, frequentemente, tm a forma de definies implcitas, enquanto as
definies explcitas so, normalmente, fceis de serem transformadas em programas. Assim, a
programao funcional prov uma forma de se passar das especificaes formais para programas
satisfazendo estas especificaes.
Exerccios.
1. Mostre que a definio explcita da funo implica (anterior) satisfaz a sua definio im-
plcita.
2. Mostre que a definio explcita da funo implica a nica soluo para a sua definio
implcita, ou seja, nenhuma outra funo booleana satisfaz estas duas equaes, apesar de
que devem existir outras formas de expressar esta mesma funo. Sugesto: usar Tabela-
verdade.
Deve ser notado que as definies recursivas so implcitas, por natureza. No entanto, como
seu lado esquerdo simples, ou seja, composto apenas pelo nome da funo e seus argumentos,
elas podem ser convertidas facilmente em regras de reescritas. Por exemplo, as duas equaes
seguintes constituem uma definio recursiva de fatorial, para n 0.
f at : N ( N
1, se n = 0
f at(n) =
n f at(n 1), se n > 0
f at n n f at (n 1), se n > 0
f at 0 1 se n = 0
Estas regras de reescrita nos dizem como transformar uma frmula contendo f at. A realizao
destas transformaes, no entanto, no elimina, necessariamente, a funo da frmula. Por
exemplo,
2 + f at 3 2 + 3 f at (3 1)
No entanto, se a computao termina, ento a aplicao repetida das regras de reescrita
eliminar f at da frmula, ou seja,
2 + f at 3 2 + 3 f at(3 1)
2 + 3 f at2
2 + 3 2 f at1
2 + 6 f at1
2 + 6 1 f at0
2 + 6 f at0
2+61
2+6
8
26
1.5.3 Definies de funes por enumerao
As funes podem ainda ser compostas para formar uma nova funo. Como exemplo, a funo
implica pode ser definida da seguinte forma:
implica : Bool Bool Bool
27
implica (x, y) = or (not x, y)
Neste caso, necessrio saber como as funes a serem compostas, or e not, so aplicadas.
A aplicao de uma funo se torna simplesmente um processo de substituio das funes
primitivas. Por exemplo, para avaliar a funo implica (F alse, T rue) necessrio fazer a
substituio dos argumentos pelas aplicaes das funes primitivas.
implica (F alse, T rue) = or (not F alse, T rue) = or (T rue, T rue) = T rue
Este processo independente do domnio, ou seja, independente de quando se est tratando
com funes sobre nmeros, ou funes sobre caracteres, ou funes sobre rvores, ou qualquer
outro tipo. Sendo a funo f definida por
f (x) = h(x, g(x))
ento
f (u(a)) = h(u(a), g(u(a)))
independente das definies de g, h, u ou da constante a.
Frequentemente, a definio de uma funo no pode ser expressa pela composio simples de
outras funes, sendo necessria a definio da funo para vrios casos. Por exemplo, a funo
que retorna o sinal algbrico de uma varivel pode ser definida da seguinte forma:
sinalg : Z Z
1, se x > 0
sinalg(x) = 0, se x = 0
1, se x < 0
De forma similar, a diferena absoluta entre x e y pode ser definida da seguinte forma:
dif abs : Z Z( Z
x y, se x > y
dif abs(x, y) =
y x, se x y
Algumas situaes existem em que necessrio definir uma funo em termos de um nmero
infinito de casos. Por exemplo, a multiplicao de nmeros naturais () pode ser definida por
infinitas aplicaes da funo de adio (+). Seno vejamos:
0, se m=0
n,
se m=1
mn= n + n, se m=2
n + n + n, se m=3
..
.
Como no possvel escrever um nmero infinito de casos, este mtodo de definio de funes
s usual se existir alguma regularidade ou algum princpio de unificao entre os casos, que
permita gerar os casos no definidos, a partir de casos j definidos. Se existir tal princpio de
unificao, ele deve ser estabelecido, sendo este o propsito das definies recursivas, onde um
28
objeto definido em termos de si prprio. Por exemplo, uma definio recursiva da multiplicao
anterior pode ser:
: N (
N N
0, se m = 0
mn=
n + (m 1) n, se m > 0
1. Dados dois nmeros naturais, x e y, ambos maiores que zero, defina uma funo mdc(x,y)
que d como resultado o mximo divisor comum entre x e y.
Soluo:
mdc : N + N + N +
x, se x = y
mdc(x, y) = mdc(x y, y), se x > y
mdc(y, x), se x < y
2. Dados dois nmeros naturais, x e y, ambos maiores que zero, defina uma funo div(x, y)
que d como resultado a diviso inteira de x por y.
Soluo:
div : N + N + N +
1, se x = y
div(x, y) = 1 + div((x y), y), se x > y
0, se x < y
3. Dados dois nmeros naturais, x e y, ambos maiores que zero, defina uma funo mod(x, y)
que d como resultado o resto da diviso de x por y.
Soluo:
mod : N + N + N +
0, se x = y
mod(x, y) = mod((x y), y), se x > y
x, se x < y
4. Dados dois nmeros naturais, x e y, ambos maiores que zero, defina uma funo mmc(x, y)
que d, como resultado, o mnimo mltiplo comum entre x e y.
Soluo:
Temos que construir uma funo auxiliar, mmcaux(x, y, k), para algum k natural e k > 0,
de forma que k seja mltiplo de x e y. O menor valor de k ser o mximo entre os valores
de x e y.
mmcaux : N + N(+ N + N +
k, se (mod(k, x) = 0)&(mod(k, y) = 0)
mmcaux(x, y, k) =
mmcaux(x, y, k + 1), senao
29
Agora pode-se definir o mnimo mltiplo comum usando esta funo auxiliar:
mmc : N + N + N +
mmc(x, y) = mmcaux(x, y, max(x, y))
onde a nova funo max, que retorna o maior valor entre dois nmeros naturais, pode
ser definida de forma simples. Deve ser observado que o mnimo mltiplo comum entre
dois nmeros tambm pode ser encontrado atravs da diviso do seu produto pelo mximo
divisor comum entre eles, ou seja, mmc(x, y) = div(x y, mdc(x, y)).
5. Dado um nmero natural n > 1, defina uma funo numdiv(n) que d como resultado o
nmero de divisores de n, incluindo o nmero 1.
Soluo:
De forma similar a que foi feita no exerccio anterior, ser construda uma funo auxiliar,
quantdiv(n, k), para algum k natural e k > 0.
quantdiv : N + N + N +
0, se n < 2k
quantdiv(n, k) = 1 + quantdiv(n, k + 1), se mod(n, k) = 0
quantdiv(n, k + 1), se mod(n, k) > 0
assim, a funo numdiv pode ser definida da seguinte forma:
numdiv : N + N +
numdiv(n) = quantdiv(n, 1)
Exerccios
30
1.6 Resumo
Este Captulo foi dedicado fundamentao terica das linguagens funcionais, buscando inser-las
no contexto matemtico. Esta fundamentao tem como vantagem, primeiramente a constatao
de que essas linguagens tm um embasamenteo terico slido e depois, sendo este embasamento
oriundo da Matemtica, torna estas linguagens mais tratveis matematicamente e mais fceis de
terem seus programas provados.
A parte histrica mostrada no incio do Captulo foi baseada na Apostila de Rafael Lins [23]
e no livro de Brainerd [6]. No entanto, para uma viagem interessante pelos meandros da histria
da Matemtica o leitor deve consultar as referncias [5, 9].
A anlise da ligao existente entre as linguagens funcionais e a Matemtica foi, grande parte,
baseada no livro de Bruce Maclennan [25]. Ele adota a metodologia de praticar e depois justificar
a parte prtica atravs da teoria. Esta metodologia faz com que o livro seja uma referncia muito
interessante no apenas pelo mtodo utilizado mas, principalmente, pelo contedo muito bem
escrito. Outra referncia importante para este estudo foi o livro de Antony Davie [8] que apresenta
um contedo prximo da parte terica do livro de Maclennan. No menos importante, foi o livro
de Chris Okasaki [28] que apresenta uma teoria de linguagens funcionais bastante profunda e
objetiva.
31
32
Captulo 2
-clculo
2.1 Introduo
Como foi mencionado na Introduo deste trabalho, o -clculo utilizado como linguagem
intermediria entre uma linguagem funcional de alto nvel e a linguagem de mquina. Desta
forma, o estudo do -clculo se torna importante para se compreender algumas construes dos
programas funcionais, sendo este o objetivo deste Captulo.
O -clculo foi desenvolvido por Alonzo Church no incio dos anos 30, como parte de um
sistema de lgica de ordem superior com o propsito de prover uma fundamentao para a
Matemtica, dentro da filosofia da escola logicista de Peano-Russel [23]. O -clculo uma
teoria que ressurge do conceito de funo como uma regra que associa um argumento a um valor
calculado atravs de uma transformao imposta pela definio da funo. Nesta concepo,
uma funo representada por um grafo, onde cada n um par (argumento, valor).
A lgica combinatorial foi inventada antes do -clculo, em cerca de 1920, por Moses Schn-
finkel, com um propsito semelhante ao do -clculo [6]. No entanto, ela foi redescoberta 10 anos
depois, em 1930, por Haskell B. Curry, que foi o maior responsvel pelo seu desenvolvimento at
cerca de 1970. Em 1935, Kleene e Rosser provaram que o -clculo e a lgica combinatorial eram
inconsistentes, o que provocou um desinteresse acadmico pelo tema. No entanto, Curry no
desistiu de seu estudo e construiu uma extenso lgica combinatorial para fins de seus estu-
dos, conseguindo sistemas mais fracos que a lgica clssica de segunda ordem. Em 1975, Dana
Scott e Peter Aczel mostraram que seu ltimo sistema apresentava modelos interessantes [6]. Em
1941, Church desistiu de sua pesquisa inicial e apresentou uma sub-teoria que lidava somente
com a parte funcional. Esta sub-teoria passou a ser chamada de -clculo puro e foi mostrada
ser consistente pelo teorema de Church-Rosser [23]. Usando o -clculo, Church props uma
formalizao da noo de computabilidade efetiva pelo conceito de definibilidade no -clculo.
Kleene mostrou que ser definvel no -clculo equivalente recursividade de Gdel-Herbrand.
Concomitantemente, Church formulou a sua conjectura associando a recursividade como a for-
malizao adequada do conceito de computabilidade efetiva. Em 1936, Allan Turing modelou a
33
computao automtica e mostrou que a noo resultante (computabilidade de Turing) equiva-
lente definibilidade no -clculo. Desta forma, o -clculo pode ser visto como uma linguagem
de programao e como tal diversos tipos de problemas de programao podem ser analisados,
principalmente os relacionados com chamadas a procedimentos [23].
Curry e seus colegas [7], Barendregt [3] e outros desenvolveram extensivamente os aspectos
sintticos do -clculo, enquanto Scott [38], principalmente reportado por Stoy [45] e Schmidt
[37], se dedicou semntica da notao, o que veio a facilitar a vida dos usurios futuros do
-clculo.
2.2 -expresses
sendo M uma -expresso e x uma varivel qualquer, ento (x.M) uma -expresso,
chamada abstrao ou funo.
As -expresses, assim definidas, podem ser formuladas utilizando a notao BNF, da seguinte
forma:
< exp > :: < constante > - constantes embutidas
|< variavel > - nomes de variveis
| (< exp >< exp >) - combinao ou aplicao
| ( < variavel > . < exp >) - abstrao ou funo.
Apesar das definies acima serem claras, elas so muito rgidas e, em alguns casos, podem
surgir dvidas em relao s regras de escopo, ou seja, at que ponto de uma -expresso uma
varivel tem influncia. Assim, as observaes a seguir devem ser utilizadas para dirimir estas
dvidas, que surgem, principalmente, por quem est inciando os primeiros passos no -clculo.
Como exemplo, comum no se reconhecer imediatamente o escopo de uma varivel e, nestes
casos, elas podem ser de grande valia.
3. Apesar da rigidez mostrada na BNF das -expresses, os parnteses devem ser usados
apenas para resolver ambigidades [51]. Por exemplo, (x.(y.(. . . (z.E) . . .))) pode ser
escrita como x.y. . . . z.E, ou ainda como xy . . . z.E, significando que as abstraes
ou funes so associadas pela direita. No entanto, grupos de combinaes ou apli-
caes de termos combinados so associados pela esquerda, ou seja, E1 E2 E3 . . . En sig-
nifica (. . . ((E1 E2 )E3 ) . . . En ). Alm disso, a.CD representa (a.(CD)) e no ((a.C)D),
34
estabelecendo que o escopo de uma varivel se extende at o primeiro parntese descasado
encontrado a partir da varivel, ou atingir o final da -expresso.
Exemplos
So exemplos de -expresses:
1. z uma varivel
2. 8 uma constante
3. zy uma combinao ou aplicao ((z)(y))
4. (a . (ab)) uma -abstrao ou funo
5. (y . y) (a . (ab)) uma combinao ou aplicao
6. (a (z . y))a uma combinao ou aplicao
7. (a . b . c . (ac)) (k . k)(x . x) uma combinao ou aplicao
Nesta seo ser mostrada a sintaxe do -clculo, ficando a semntica para uma prxima. A
sintaxe importante na construo de -expresses de forma correta.
Em sua forma mais pura, o -clculo no tem funes embutidas, como +, *, - e /. No entanto,
ser acrescentada uma coleo de tais funes como uma extenso ao -clculo puro, uma vez
que o nosso objetivo termos um -clculo que seja aplicvel.
Entre as funes embutidas que sero includas, citamos:
A escolha de funes embutidas arbitrria e, assim sendo, elas sero adicionadas medida
que as necessidades surgirem. Deve ainda ser mencionado que:
do ponto de vista de implementao, um programa funcional deve ser visto como uma
-expresso que vai ser avaliada;
35
a avaliao de uma -expresso se d pela seleo repetida de -expresses redutveis,
conhecidas como redexes, e suas redues. Por exemplo, a -expresso (+( 2 3) ( 8 2))
apresenta dois redexes: um ( 2 3) e o outro ( 8 2), apesar da expresso toda no
representar um redex porque seus parmetros ainda no esto todos avaliados. A avaliao
da -expresso acima deve ser feita obedecendo a uma das seguintes seqncias de redues:
1. (+( 2 3) ( 8 2)) (+ 6( 8 2)) (+ 6 16) 22 ou
2. (+( 2 3) ( 8 2)) (+( 2 3) 16) (+ 6 16) 22
2.3.2 -abstraes
Deve ser observado que as funes em uma linguagem de programao convencional tm,
obrigatoriamente, um nome, enquanto, no -clculo, as -abstraes so funes annimas.
A semntica operacional do -clculo diz respeito s regras utilizadas para converter uma -
expresso em outra. Na realidade, existem 3 (trs) regras de converso. Antes de serem mostradas
estas regras, devemos definir alguns termos que so utilizados na aplicao destas regras.
Uma idia central no estudo das linguagens de programao, da notao matemtica e da
lgica simblica a de ocorrncia livre e ocorrncia ligada (ou conectada) de uma varivel. Esta
36
idia, normalmente, provoca confuso em quem est vendo o tema pela primeira vez. Assim, ele
ser introduzido de forma gradual e intuitiva, utilizando exemplos j conhecidos da Matemtica.
Assim, vamos considerar o somatrio:
n
X
i2 + 1
i=1
Nesta expresso, a varivel i uma varivel ligada, ou seja, ela est fortemente atrelada
ao somatrio. Diz-se que ela ocorre ligada nesta expressso. Uma caracterstica importante
das variveis ligadas que elas podem ser renomeadas sem que o significado da expresso seja
alterado. Por exemplo, o somatrio anterior pode tambm ser representado da seguinte forma,
sem haver qualquer modificao em seu significado:
n
X
k2 + 1
k=1
Na teoria dos conjuntos, o conjunto de todos os x (ligada) tal que x 0 o mesmo conjunto
de todos os y (ligada) tal que y 0, ou seja,
{x | x 0} {y | y 0}
Tambm a proposio para todo x, x+1>x" equivalente proposio para todo y, y+1>y",
ou seja,
n
X
i (j 2 + i a)
j=1
J sabido que a ocorrncia da varivel j ligada. Isto pode ser verificado pelo fato de
que ela pode ser trocada por qualquer outra varivel, desde que no seja i ou a, sem mudar o
significado da expresso. Por exemplo, a expresso
n
X
i (k 2 + i a)
k=1
37
em uma ocorrncia livre de uma varivel ela for trocada por outra, o significado da expresso
tambm modificado. Alm disso, uma varivel pode ocorrer ligada em uma expresso e livre
em outra. Por exemplo, a varivel i ocorre livre em
n
X
j2 + i a
j=1
A varivel j ocorre ligada nesta expresso e pode ser trocada por outra, por exemplo, k.
n
X
Aik = Ai1 + Ai2 + . . . + Ain
k=1
Seja a -expresso (x . + x y) 4. Pelo que foi visto anteriormente, sabe-se que se trata da
aplicao de uma funo sobre a varivel x, onde o corpo da funo (+ x y), a uma outra
-expresso, que no caso a constante 4.
A varivel x pode ser pensada como um local onde o argumento 4 deve ser colocado. J para
a varivel y, este mesmo raciocnio no pode ser aplicado porque no existe este local sinalizado
por uma varivel y aps uma letra . Isto significa que as duas variveis (x e y) tm status
distintos.
38
Formalmente, define-se: uma ocorrncia de uma varivel ligada se existir uma -abstrao
qual esta varivel esteja ligada. Em caso contrrio, a ocorrncia livre. A Figura 2.1 mostra
um exemplo contendo ocorrncias livres e ligadas de variveis.
ligada
ligada
x.+(( y.+yz)7)x
^
ocorrencias: livre
Deve ser observado que uma mesma varivel pode ocorrer livre em um ponto de uma -
expresso, e ligada em outro, como o caso da varivel x na -expresso + x ((x . + x 1) 4).
A primeira ocorrncia de x livre e a segunda ligada.
Podemos dizer, sendo x e y duas variveis e e duas -expresses, que:
a) x livre em y se x = y. Se x 6= y, a ocorrncia de x ligada.
b) x livre em y . M se (x for livre em M ) E (x 6= y)
c) x livre em M N se (x for livre em M ) OU (x for livre em N )
Exemplos
2.4.2 Combinadores
Uma -expresso que no apresenta variveis livres chamada fechada ou combinador. Existem
alguns combinadores que desempenham um papel especial no estudo do -clculo, conforme foi
afirmado na Introduo desta Apostila, onde foi feita referncia aos combinadores S, K e I, nos
quais baseada a mquina de reduo-SK de Turner.
A criao de combinadores uma tarefa que no tem explicao lgica. Seus construtores de-
vem ter tido algum insight, no entanto no existem explicaes plausveis de alguma metodologia
de construo destas -expresses especiais.
Os combinadores mais conhecidos so:
I = x.x Identidade
K = x.y.x Projeo
S = x.y.z.xz(yz) Composio
= x.xx Duplicao
Y = f.(y.f (yy))(y.f (yy)) Usado na representao de funes recursivas
= a.b.b(aab) Tambm usado na representao de funes recursivas
Exerccios resolvidos
39
2. Identifique nas expresses abaixo as ocorrncias livres e as ligadas das variveis
a) x.xx As duas ocorrncias da varivel x so conectadas
b) (x.y.x)x O ltimo x ocorre livre
c) (x.y.xx)xa As duas ltimas variveis ocorrem livres
d) (x(x.y))x Todas as variveis ocorrem livres
Exerccios propostos
(a) (x(y)z)
(b) (x.y.x((i.ii)(b.c.b)(b.c.b))
(c)
(d) x.x
2. Identifique as ocorrncias livres e as ocorrncias ligadas das variveis nas expresses abaixo:
(a) (x.xwx)9
(b) (x.y.x)3b
(c) (x.yxx)(i.i)5
(d) (z.b.c.ac(bc)f )(b.c.b)(b.c.b)
(e) (x.y.y)w((z.zzz)(w.www))((a.a)(a.b))
(a) (x.xx)9
(b) (x.y.x)3
(c) (x.yxx)(i.i)5
(d) (a.b.c.ac(bc)f )(b.c.b)
(e) (x.y.y((z.zzz)(w.ww))(a.a)(a.x))
40
2.5 Regras de converso entre -expresses
2.5.1 -converso
1. (x. x 1) (y. y 1)
2. x.x y.y z.z
3. x.axa b.aba
2.5.2 -converso
1. (x . + x x) 5 + 5 5 = 10 (veja que 5 ocorre livre em + x x)
2. (x . 3) 5 3 (veja que 5 ocorre ligada em 3)
41
3. (x . (y . y x))4 5 (y . y 4)5 5 4 = 1
4. (x.xax)(x.x) (x.x)a(x.x)a(x.x)
5. (x.y.z.x(yz))(x.x) y.z.(x.x)(yz) y.z.yz y.y
Observaes:
1. Nas redues acima, pode ser observada a currificao em ao, ou seja, as aplicaes das
-abstraes retornando uma outra funo (-abstrao) como resultado.
2. Podemos usar -reduo no sentido oposto, para encontrar uma nova -abstrao. Por
exemplo, + 4 1 (x . + x 1) 4. A isto chamamos de -abstrao. Uma -converso
um termo genrico utilizado tanto para uma -reduo quanto para uma -abstrao,
simbolizando-se por . Por exemplo, para este caso, + 4 1 (x . + x 1)4.
3. Para o caso de se ter uma funo como argumento, as substituies so realizadas da mesma
forma. Por exemplo, (f . f 3)(x . + x 1) (x . + x 1)3 + 3 1 = 4
2.5.3 -converso
Diz-se que uma -expresso se -converte a uma outra -expresso , se existir uma seqncia
de -expresses < 1 , 2 , . . . , n >, onde
a) = 1 e n = e
b) i i+1 ou i+1 i , para i = 1, 2, . . . , n
42
2
4
=
1
=
3 5
1. Verifique se K(I) a.(III).
2. Verifique se x.(y.Iy)x x.I(y.xy).
3. Verifique se SK KI.
2.5.5 Captura
Deve ser tomado algum cuidado na escolha dos nomes dos parmetros, uma vez que mais de um
deles podem ter o mesmo nome. Por exemplo,
(x . (x . + ( x 1))x 3)9 linha 1
(x . + ( x 1))9 3 linha 2
+( 9 1)3 linha3
= + 8 3 = 11 linha 4
Como visto, o x interno de ( x 1) da linha 1 deste exemplo no foi substitudo na linha 2
porque ele no ocorre livre em (x . + ( x 1)) que o corpo da -abstrao mais externa.
O uso de nomes de variveis pode algumas vezes criar situaes confusas envolvendo -
redues. Por exemplo, seja a -expresso (x . (y . yx))y. O y mais externo desta -expresso
ocorre livre. Se executarmos uma -reduo vamos obter a -expresso y . yy. Esta -expresso
resultante apresenta um significado distinto da anterior porque o y externo, que era livre, tornou-
se conectado.
Esta situao conhecida como o problema de captura de variveis e pode provocar ambigu-
idades na avaliao mecnica de -expresses. A soluo trivial para este problema efetuar
-converses antes de realizar a -converso para evitar que variveis distintas, mas homnimas,
sejam confundidas. No caso da -expresso citada, uma seqncia correta de redues seria:
(x . (y . yx))y (x . (z . zx))y z . zy
43
O mesmo problema pode ser verificado na -expresso (x . z . xz)(y . yz) que seria -
redutvel a (z . (y . yz)z) e o z de (y . yz) perderia seu contexto original, sendo capturado pelo
z de (x . z . xz). Neste caso, uma possvel seqncia de redues seria (x . z . xz)(y . yz)
(x . a . xa)(y . yz) (a . (y . yz)a) y . yz.
Este problema tambm pode aparecer nas linguagens de programao imperativas tradi-
cionais, como C, Java e outras. Suponhamos que na declarao de um procedimento se utilize
uma varivel local denominada x e que na execuo deste mesmo procedimento ele receba como
parmetro real uma varivel tambm denominada x, declarada fora do procedimento, portanto
no local. Uma referncia varivel no local x ser feita, na realidade, varivel x local, ou
seja, a varivel x externa foi capturada pela varivel x interna. Isto acontece porque as variveis
tm o mesmo nome (so homnimas), apesar de representarem entidades distintas.
Exemplos resolvidos.
2. As funes embutidas podem ser construdas como quaisquer outras. Por exemplo,
CON S = (a . b . f . f a b),
HEAD = (c . c(a . b . a)) e
T AIL = (c . c(a . b . b)).
Vejamos agora uma seqncia de redues envolvendo estas duas funes:
HEAD (CONS p q) = (c . c(a . b . a)) (CONS p q)
(CONS p q) (a . b . a)
= ((a . b . f . f a b) p q) ( a . b . a)
((b . f . f p b) q) (a . b . a)
(f . f p q)(a . b . a)
(a . b . a) p q
(b . p) q
p
44
2.5.6 Provando a conversibilidade
Muito freqentemente, nos deparamos com casos em que a prova de conversibilidade entre duas
-abstraes bastante complexa. Por exemplo, as -expresses IF T RU E ((p . p) 3) e
(x . 3) denotam a mesma funo, ou seja, a funo que sempre retorna o valor 3, independente
dos valores dos argumentos reais. Assim, espera-se que elas sejam conversveis uma na outra, j
que denotam a mesma funo. Realizando as -converses sobre a primeira, temos:
IF T RU E ((p . p) 3)
IF T RU E 3
(x . IF T RU E 3 x)
= (x . 3) - pela definio de IF.
Um mtodo alternativo utilizado para provar a conversibilidade de duas -expresses que
denotam a mesma funo, consiste em aplicar ambas as -expresses a um mesmo argumento
arbitrrio, por exemplo, w. Na Tabela a seguir est mostrado como feita a prova de conversibil-
idade das duas funes anteriores, utilizando esta tcnica.
IF TRUE ((p . p) 3) w (x . 3) w
(p . p) 3 pela def de IF 3
3
A aplicao das regras de converso nem sempre to simples e direta como as mostradas nos
exemplos anteriores. Vamos introduzir agora uma notao bastante utilizada na converso de
-expresses, principalmente na implementao computacional de redutores, por ser bastante
intuitiva.
A notao E[M/x] significa que, na expresso E, todas as ocorrncias livres de x sero substi-
tudas por M. Esta notao mostra explicitamente qual varivel est sendo trocada e por quem.
Deve ser notada a particularidade da notao E[M/x] que, apesar de representar o termo lido
como M por x" em portugus, representa a troca de x por M". Esta inverso de significado
deve-se to somente diferena de interpretao existente entre o portugus e o ingls, que foi o
idioma no qual a notao foi descrita.
Esta notao tambm pode ser utilizada em -converses. A Tabela 2.3 mostra um resumo
desta notao.
Exemplos
1. x[/x] =
2. x[/y] = x
45
Tabela 2.3: Resumo das converses.
x[M/x] = M
c[M/x] = c, onde c uma constante distinta de x
(E F) [M/x] = E [M/x] F[M/x]
(x . E) [M/x] = x . E
(y . E) [M/x] onde y qualquer varivel distinta de x
= y . E[M/x], se x no ocorre livre em E OU y no ocorre livre em M
= z . (E[z/y]) [M/x], onde z o nome de uma nova varivel que no
ocorre livre em E ou em M.
Definies:
-converso: se y no livre em E, ento (x . E) (y . E[y/x])
-converso: (x . E) M E[M/x]
-converso: se x no livre em E e E denota uma funo, ento (x . E x) E
3. (abx)(xy)[/x] = (ab)(y)
4. (axxb)(bx)(xx)[/x] = (ab)(b)()
5. (a.xy)(x.xy)[S/x] = (a.Sy)(x.xy)
6. S[K/x] = S
7. (x.xy)[a.xy/y] = z.z(a.xy)
8. (x.xax)(x.x) xax[x.x/x] = (xa[x.x/x])(x[x.x/x)] =
((x[x.x/x])a[x.x/x])(x.x) = (x.x)(a)(x.x) (x[a/x])(x.x) = a(x.x)
Exerccios Reduza, cada uma das expresses a seguir, a sua forma normal, se existir.
1. (((f.x.y.(x)(f )y)p)q)r.
2. (((x.y.z.(y)x)(x)y)(u)z)y.
3. (((x.y.z.((x)z)(y)z)(u.v.u)w)s.s)t.
4. (((x.(z.(x)(y)y)y.(x)(y)y)z.u.v.(u)(z)v)(r.s.r)t)w.
5. (x.x(xy))N .
6. (x.y)N .
7. (x.(y.xy)N )M .
8. y.(xy.yx)(z.yz).
9. (xy.xy)((y.yy)(y.yy)).
10. (x.xx)(xy.xy).
11. (y.y(x.xx))(xy.yx).
12. xy.x(z.y(w.zw)).
13. (xy.x)(x.x)(x.y(xx)).
14. (xy.xy(x.x))(x.xx)(x.y(xx)).
15. (xy.xy(x.x))(x.xx)(x.x(xx)).
46
2.6 Ordem de reduo
Um redex (red uction ex pression) uma -expresso na qual todos os parmetros necessrios
para que uma operao possa ser feita esto prontos para serem utilizados. Se uma expresso
no contiver qualquer redex a sua avaliao est completa e, neste caso, diz-se que a expresso
est na sua forma normal.
No entanto, pode acontecer que uma expresso tenha mais de um redex e, neste caso, existem
mais de um caminho a serem seguidos na seqncia de avaliaes. J sabemos, pela propriedade
de Church-Rosser, que a utilizao de qualquer uma das seqncias de reduo leva ao mesmo
resultado, se ele existir. Por exemplo, + ( 3 4) ( 7 8) apresenta dois redexes: 3 4 e 7 8. Se
for escolhido o redex mais esquerda teremos a seguinte seqncia de redues:
+ ( 3 4) ( 7 8)
+ 12 ( 7 8)
+ 12 56
68
Se, no entanto, a escolha recair sobre o redex mais direita, a seqncia de reduo ser:
+ ( 3 4) ( 7 8)
+ ( 3 4) 56
+ 12 56
68
Apesar dos resultados das duas seqncias de avaliao serem iguais, algumas observaes
devem ser feitas:
1. nem toda -expresso tem uma forma normal. Por exemplo, a -expresso (), onde
= (x . x x), tem a seguinte seqncia de redues:
(x . x x) (x . x x) (x . x x) (x . x x) (x . x x) (x . x x) . . .
2. Algumas seqncias de reduo podem atingir a forma normal, enquanto outras no. Por
exemplo, (x . 3) () pode ser avaliada para 3, escolhendo o primeiro redex, mas, se
for escolhido o segundo ( aplicado a ), o resultado ser um loop infinito, sem atingir a
forma normal.
At este ponto, temos insistido, mesmo sem provas, que seqncias diferentes de redues
no podem levar a formas normais diferentes. Dois teoremas, descritos a seguir, mas sem demon-
straes, garantem esta propriedade. Sugere-se ao leitor pesquisar estas demonstraes na bibli-
ografia indicada, por no fazer parte do objetivo desta Apostila. Estas demonstraes requerem
conhecimentos matemticos e da Teoria da Computao mais avanados dos que os exigidos
como pr-requisitos para este estudo.
Teorema 1 de Churh-Rosser (CRT-I).Se E1 E2 , ento existe uma expresso E tal
que E1 E e E2 E".
Prova: Exerccio.
Corolrio. Nenhuma expresso pode ser convertida a duas formas normais distintas.
Prova: Suponha que uma -expresso, E, seja redutvel a duas -expresses distintas, E1 e E2
e que E1 e E2 estejam na forma normal. Pelo teorema CRT-1, anterior, existe uma -expresso
1 , tal que E 1 e E1 1 . Pelo mesmo teorema, tambm existe uma -expresso 2 , tal
47
que E 2 e E2 2 . Mas E1 e E2 esto na forma normal no podem ser redutveis, o que
uma contradio. Esta contradio se originou do pressuposto de que existiam duas formas
normais distintas para a mesma -expresso. Portanto, impossvel que uma -expresso tenha
mais de uma forma normal.
Informalmente, todas as seqncias de redues que terminam, chegaro inequivocamente ao
mesmo resultado.
Este teorema de Church-Rosser conhecido como teorema da atingibilidade e unicidade da
forma normal e tem uma longa histria. Ele foi primeiramente demonstrado por Alonzo Church
e J. Barkley Rosser em 1936 [25] e esta demonstrao era to longa e complicada que muito do
trabalho de pesquisa posterior foi dedicado descoberta de formas mais simples de demonstrao
deste teorema. Assim, ele foi demonstrado de vrias formas, para diferentes propsitos, todas
elas apresentando alguma dificuldade. Uma demonstrao mais simples foi apresentada pelo
prrio J. Barkley Rosser em 1982 e uma verso mais rigorosa foi feita por Barendregt em 1984
[25].
Um esquema de prova exibido por Maclennan se baseia na descrio de uma propriedade,
conhecida como propriedade do diamante. Diz-se que uma relao R tem a propriedade do
diamante se e somente se, para todas as frmulas bem formadas X, X1 e X2 vale: se X R X1
e X R X2 , ento existe uma frmula bem formada X tal que X1 R X e X2 R X. Este esquema
pode ser visualizado graficamente na Figura 2.3.
X
1 X1
1. (x.a.(b.bab)(xa))(K)
2. x(a.aa)((b.(a.b)b)(a.aa))(x.xxx)
3. a.b.Sa(b)b
48
(x . xx)((y . y)(z . z)) ((y . y)(z . z))((y . y)(z . z))
(z . z)((y . y)(z . z))
(y . y)(z . z)
z . z
A escolha pela ordem normal de reduo garante encontrar a forma normal, se ela existir.
No entanto, isto no quer dizer que este seja o melhor mtodo a ser utilizado na reduo de -
expresses. Normalmente, este caminho o que apresenta o pior desempenho. Ento por que este
caminho deve ser seguido? A resposta que a ordem normal de execuo apresenta um mtodo
que pode ser implementado computacionalmente e isto o que se busca em nosso estudo, ou seja,
procura-se um mtodo para o qual exista um procedimento mecnico para segu-lo e enontrar a
forma normal, se ela existir. Na realidade, muitas otimizaes foram feitas buscando melhorar o
desempenho deste procedimento que, em ltima anlise, representam melhorias de desempenhos
na execuo de programas funcionais. Como exemplo de apenas uma destas otimizaes pode-se
citar David Turner [47].
Nesta definio, damos um nome a uma -abstrao (FAT ) e nos referimos a ele mesmo, den-
tro da -abstrao. Este tipo de construtor no provido pelo -clculo porque as -abstraes
so funes annimas e, portanto, elas no podem fazer referncias a nomes.
Vamos colocar a -expresso F AT em uma forma mais adequada ao nosso desenvolvi-
mento. Teremos ento F AT = n . (. . . F AT . . .), onde os pontos representam as outras
49
partes da funo que no nos interessam, neste momento. Fazendo uma -abstrao em F AT ,
transformamo-la em F AT = (f . (n . (. . . f . . .))) F AT , sendo f uma varivel.
Esta funo pode ser escrita na forma F AT = HF AT onde, H = (f . (n . (. . . f . . .))).
Esta definio de H adequada aos nossos propsitos, uma vez que ela uma -abstrao
ordinria e no usa recurso. A equao F AT = H F AT estabelece que quando a funo H
aplicada a F AT o resultado o prprio F AT . Ento, F AT um ponto fixo de H.
Vamos agora procurar um ponto fixo genrico para H. Para isto vamos criar uma funo,
Y , que toma H como argumento e retorna um ponto fixo da funo, como resultado. Assim Y
deve ser tal que Y H seja um ponto fixo de H. Portanto, H(Y H) = Y H. Por este motivo, Y
chamado de combinador de ponto fixo. Se formos capazes de produzir tal combinador, nosso
problema estar resolvido.
Como Y H retorna um ponto fixo de H e F AT um destes pontos, ento F AT = Y H. Esta
definio no recursiva e atende s nossas necessidades. Para verificar isto, vamos computar
(F AT 1) utilizando as definies de F AT e de H, dadas anteriormente e que o mecanismo de
clculo obedece ordem normal de reduo ou seja, leftmost-outermost.
F AT = Y H
H = f . n . IF (= n 0) 1 ( n(f ( n 1)))
Ento
FAT 1 = YH 1
= H (Y H) 1
= (f . n . IF (= n 0) 1 (* n(f (- n 1))) (Y H) 1
(n . IF (= n 0) 1 (* n ((Y H) (- n 1)))) 1
(IF (= 1 0) 1 (* 1 ((Y H)(- 1 1))))
= (* 1 ((Y H) (- 1 1))) pela definio de IF
= (* 1 ((H (Y H)) (- 1 1))) pelo fato de YH ser um ponto fixo de H
= (* 1 ((f . n . IF (= n 0) 1 (* n (f (- n 1)))) (Y H) (- 1 1)))
(* 1 (n . IF (= n 0) 1 (* n (Y H (- n 1)))) (- 1 1))
(* 1 (IF (= (- 1 1) 0) 1 (* (- 1 1) (Y H (- (- 1 1) 1)))))
(* 1 (IF (= 0 0) 1 (* 0 (Y H (- 0 1)))))
(* 1 (IF TRUE 1 (* 0 (Y H (- 0 1)))))
(* 1 1) pela definio de IF
=1
A forma como o combinador Y definido j foi mostrada anteriormente, no entanto ele ser
novamente visto aqui, usando apenas algumas renomeaes de variveis:
Y = h . (x . h (x x)) (x . h (x x)).
Vamos agora avaliar Y H.
YH = (h . (x . h (x x)) (x . h (x x))) H
(x . H (x x)) (x . H (x x))
H ((x . H (x x)) (x . H (x x)))
= H (YH)
Este resultado confirma o fato de que Y H um ponto fixo de H ou seja, o combinador Y ,
quando aplicado a uma funo, retorna um ponto fixo desta funo.
50
2.8 Algumas definies
Para terminar este Captulo, vamos deixar algumas definies de funes e de outros objetos.
Por exemplo, os nmeros naturais podem ser definidos como -expresses, conhecidas como os
numerais de Church. Deve ser observado que algumas definies utilizam o caractere $ quando
referenciam outras definies, para diferenciar de alguma varivel.
3. I = x.x
4. K = x.y.x
5. = (x.x x)(x.x x)
6. S = f.g.x.f x (g x)
51
28. true = x.y.x
2.9 Resumo
Este Captulo foi todo dedicado ao estudo do -clculo. Ele se fez necessrio dada a importncia
que esta teoria tem na fundamentao das linguagens funcionais. Seu papel semelhante ao
desempenhado pela linguagem Assembly na traduo de programas em linguagens imperativas
para o cdigo de mquina. No entanto, deve ser salientado que esta teoria matemtica no
to simples como aqui parece. Esta abordagem simples foi adotada, dado o carter introdutrio
exigido para se entender como o -clculo utilizado na compilao de linguagens funcionais.
Quem quizer se aprofundar neste tema deve consultar a bibliografia especfica. As notas
de aula de Rafael Lins [23] e de Peter Welch [51] representam um bom comeo, dada a grande
quantidade de exerccios indicados e resolvidos. Quem estiver interessado em detalhes mais apro-
fundados sobre a implementao do -clculo deve consultar o livro de Peyton Jones [32]. Uma
abordagem mais terica desta linguagem, pode ser encontrada nos livros de Bruce MacLennan
[25] e de Antony Davie [8].
52
Captulo 3
3.1 Introduo
Os Captulos anteriores foram feitos com o objetivo de servirem como preparao e fundamen-
tao para o estudo das linguagens funcionais, em particular, de Haskell. Este Captulo e os
seguintes so todos dedicados codificao de programas funcionais utilizando esta linguagem.
A comunidade das linguagens funcionais tem dado a Haskell uma ateno especial e, por este
motivo, muita pesquisa tem sido feita tentando dot-la de caractersticas que a torne uma lin-
guagem de uso popular. Estas caractersticas foram citadas por Philip Wadler [50] e analisadas
na Introduo desta Apostila.
A histria de Haskell, desde a sua concepo at seu estado atual, est bem documentada
em um artigo escrito por Paul Hudak, John Hughes, Simon Peyton Jones e Philip Wadler [12].
Estes autores, alm de outros, participaram de todo o processo de criao e desenvolvimento da
linguagem e, por este motivo, contam com detalhes toda a trajetria de Haskell, desde a sua
concepo em Setembro de 1987, em Portland, no Oregon.
Haskell uma linguagem funcional pura, no estrita, fortemente tipada, cujo nome uma
homenagem a Haskell Brooks Curry, um estudioso da lgica combinatorial e um dos mais proemi-
nentes pesquisadores sobre -clculo [4, 46]. uma linguagem baseada em scripts, que consistem
em um conjunto de definies associadas a nomes, em um arquivo.
A primeira edio do Haskell Report, verso 1.0, foi publicada em 1 de abril de 1990 por Paul
Hudak e Philip Wadler com o bjetivo de ser usada como fonte de pesquisa em projetos de lingua-
gens de programao, ou seja, desejava-se que a linguagem estivesse sempre em evoluo. No en-
tanto, Haskell comeou a se tornar popular e isto obrigou seus projetistas a repensarem o objetivo
inicial e resolveram nomear Haskell 98" como uma instncia de Haskell que deveria ser estvel
o bastante para que textos e compiladores pudessem ser construdos sem muita mudana[12]. A
partir de ento, Haskell98 passou a ser considerada a verso oficial a ser utilizada at a definio
de Standard Haskell. No entanto, a linguagem continua sendo pesquisada buscando a criao de
novas Bibliotecas a serem incorporadas ao sistema. Tambm muitas extenses esto sendo in-
corporadas, como Haskell concorrente, IDLs (Interface Description Language), HaskellDirect
e interfaces para C e C++, permitindo integrao com estas e outras linguagens. Em particular,
53
tem sido desenvolvida AspectH, uma extenso de Haskell para suportar orientao a aspectos
[2], e HOpenGl, uma extenso para OpenGL, entre outras.
O site oficial na WEB sobre Haskell http://www.haskell.org, onde muitas informaes
podem ser encontradas, alm de vrios links para compiladores e interpretadores para ela. Para
produzir cdigo executvel de mquina, foram desenvolvidos vrios compiladores.
Na Universidade de Glasgow, foi construdo GHC (Glasgow Haskell Compiler ) disponvel
para ambientes UNIX (Linux, Solaris, *BSD e MacOS-X) e tambm para Windows. GHC foi
iniciado em Janeiro de 1989 e , provavelmente, o compilador mais completo em atividade,
um projeto open-source com licena liberal no estilo BSD. Sua primeira verso foi escrita por
Kevin Hammond em LML, cujo prottipo ficou pronto em Junho de 1989, quando um time de
pesquisadores da Universidade de Glasgow constitudo por Cardelia Hall, Will Partain e Peyton
Jones resolveram reimplement-lo em Haskell (bootstrapping), com um parser construdo em
Yacc e em C. Esta implementao ficou pronta no outono de 1989 [12]. GHC est disponvel em
http://www.dcs.gla.ac.uk/fp/software/ghc/
Na Universidade de Chalmers, Lenhart Augustsson implementou Haskell em LML, um com-
pilador que ficou conhecido como hbc, uma referncia a Haskell Brooks Curry, o pesquisador
a quem a linguagem deve o nome. HBC est disponvel em http://www.cs.chalmers.se/ au-
gustss/hbc.html.
Tambm est disponvel o compilador nhc, considerado fcil de ser instalado, com heap
profiles, muito menor que os outros congneres, disponvel para todos os padres UNIX e
escrito em Haskell 98. O compilador nhc foi originalmente desenvolvido por Niklas Rgemo, um
aluno de Doutorado na Universidade de Chalmers [35]. Sua motivao inicial foi construir um
compilador mais eficiente em termos de espao e memria que os sistemas GHC e hbc. Para isto
ele utilizou muitas ferramentas de heap-prifiling que haviam sido desenvolvidas em York e que
revelavam muito desperdcio de memria em hbc. Para isto, ele resolveu fazer Ps-Doutorado em
York em colaborao com Colin Runciman, onde desenvolveram vrios mtodos de heap-profiling
para detectar ineficincias de espao, produzindo uma verso muito eficiente de nhc.
54
Helium. O compilador Helium, baseado em Utrecht, destinado especialmente ao ensino
da linguagem, dando ateno especial s mensagens de erros.
UHC e EHC. Ultrecht tambm tem sido bero de outros projetos de compiladores para
Haskell. UHC e EHC so alguns deles disponveis em http://www.cs.uu.n1/wiki/Center/Re-
searchProjects.
jhc. Este um novo compilador, desenvolvido por John Meacham, focalizado em otimiza-
o agressiva, usando anlise de programas, permitindo uma tcnica deferente na imple-
mentao de type class. Baseado no trabalho anterior de Johnsson e Boquist, jhc usa
anlise de fluxo para suportar uma representao de thunks que pode ser extremamente
eficiente.
Apesar de todo este esforo em busca de desempenho, GHC e hbc foram implementados em
uma linguagem funcional e requeriam muita memria e espao em disco. em Agosto de 1991,
Mark Jones, um estudante de Doutorado na Universidade de Oxford, resolveu fazer uma imple-
mentao inteiramente diferente que ele chamou de Gofer (GOod For Equational Reasoning).
Gofer um interpretador implementado em C, desenvolvido em um PC 8086 a 8 MHz, com
640KB de RAM e que cabia em nico disquete de 360KB. Na realidade, ele queria aprender mais
sobre a implementao de linguagens funcionais e algumas caractersticas incorporadas por ele
apresentam algumas diferenas entre os sistemas de tipos de Haskell e Gofer, fazendo com que
programas escritos em uma no funcionasse na outra implementao.
No vero de 1994, Mark Jones deixou a Universidade de Yale e foi com seu grupo para a
Universidade de Nottingham, onde desenvolveram Hg, um acrnimo para Haskell-gofer, que
logo adquiriu o nome de Hugs (Haskell Users Gofer System). Esta implementao, codificada
em C, pequena, fcil de ser usada e disponvel para vrias plataformas, incluindo UNIX,
Linux, Windows 3.x, Win32, DOS e ambiente Macintosh. Atualmente, Hugs se encontra estvel
com a padronizao de Haskell 98, apesar de muitos pesquisadores continuarem trabalhando
na incorporao de suporte e novas caractersticas linguagem, como parmetros implcitos,
dependncias funcionais, .NET da Microsoft, uma interface funcional, mdulos hierrquicos,
caracteres Unicode e uma coleo de bibliotecas.
Existem duas formas nas quais um texto considerado um programa em Haskell. A primeira delas
considerar todo o texto como um programa, exceto o que comentado, que pode ser de duas
maneiras: com - -, que representa um comentrio at o final da linha corrente, ou envolvendo o
comentrio com os smbolos {- e -}, podendo englobar vrias linhas. Os arquivos em Haskell
com este tipo de programa devem ter a extenso .hs. Esta a maneira mais utilizada pelos
programadores de Haskell.
A outra forma considerar todo o texto como comentrio e sinalizar o que deve ser realmente
um programa iniciando a linha com o sinal > identado. Neste caso, o arquivo deve ter extenso
.lhs. Vamos mostrar um exemplo de cada situao.
{- ######################################################################
exemplo.hs
Este arquivo eh um exemplo de um arquivo .hs. Deve ser editado como arquivo
texto e salvo com a extensao .hs.
#########################################################################-}
valor :: Int -- Uma constante inteira
valor = 39
55
novalinha :: Char
novalinha = \n
resposta :: Bool
resposta = True
maior :: Bool
maior = (valor > 71)
Agora vamos mostrar o mesmo exemplo usando a viso de literal, com a extenso .lhs. Deve
ser observado que os sinais de incio e fim de comentrios desaparecem.
##########################################################################
exemplo.lhs . Este arquivo eh um exemplo de um arquivo .lhs.
##########################################################################
A identao em Haskell importante. Ele usa um esquema chamado layout para estruturar
seu cdigo [36], a exemplo de Python. Este esquema permite que o cdigo seja escrito sem a
necessidade de sinais de ponto e vrgula explcitos, nem de chaves.
Neste caso, a identao do texto tem um significado bem preciso, descrito em trs regras
funcamentais:
se uma linha comea em pelo menos uma coluna frente (mais direita) do incio da linha
anterior, considerada a continuao da linha precedente,
56
se uma linha comea na mesma coluna que a do incio da linha anterior, elas so consider-
adas definies independentes entre si e
se uma linha comea em pelo menos uma coluna anterior (mais esquerda) do incio da
linha anterior, ela no pertence mesma lista de definies.
Para o usurio executar qualquer funo do Prelude necessrio apenas chamar esta funo
na linha de comandos (prompt), disponvel aps o interpretador ser carregado, seguida de seus
argumentos e apertar a tecla Enter. O resultado desta execuo exibido imediatamente
na linha seguinte ao prompt". Assim, o interpretador tambm pode ser usado como uma
calculadora para avaliar expresses aritmticas, booleanas, logartmicas, trigonomtricas, etc. A
forma de utilizao a mesma j citada. Algumas destas funes ou operadores aritmticos esto
mostrados na Tabela 3.2.
57
utilizada na Matemtica. J as funes so normalmente declaradas como pr-fixas por ser a
forma mais utilizada nas demais linguagens de programao. No entanto, os operadores infixos
podem ser utilizados como pr-fixos, apenas colocando o operador entre parnteses. Por exemplo,
o operador + (infixo) pode ser aplicado como (+) 2 3 (pr-fixo). Os operadores pr-fixos
tambm podem ser aplicados como infixos, apenas colocando o operador entre aspas simples
(o acento agudo em ambos os lados do operador). Por exemplo, maxi 3 4 (pr-fixo) pode ser
utilizado como 3 maxi 4 (infixo).
possvel tambm trocar a associatividade ou a prioridade de um operador. Para isso,
necessrio declarar, explicitamente, o tipo de associatividade e a prioridade da funo. Por
exemplo, para trocar a associatividade e a prioridade da funo toma, pode-se fazer a declarao:
Infixl 7 toma
significando que a funo toma passa a ser infixa, associando-se pela esquerda e tem um nvel
de prioridade 7. Se a prioridade for omitida, ser considerada igual a 9, por default. Seguem
alguns exemplos de chamadas calculadora de expresses ou de funes.
Exemplos
:? 2 + 3 <enter>
5
:? (1 * 6) == (3 div 5) <enter>
False
Deve ser lembrado aqui que Haskell uma linguagem funcional pura e, como tal, no permite
atribuies destrutivas, ou seja, no possvel fazer atualizaes de variveis em Haskell. Nos
exemlos mostrados anteriormente, a varivel resposta ter o valor True enquanto o script estiver
ativo. Se for necessrio armazenar um outro valor, ter que ser criada uma outra varivel para
isto. Desta forma, em Haskell, as variveis so consideradas constantes, uma vez que elas no
podem ser atualizadas.
As palavras reservadas da linguagem so sempre escritas em letras minsculas. Haskell ap-
resenta 22 palavras reservadas, mostradas a seguir:
case else infix module type
class hiding infixl of where
data if infixr renaming
default import instance then
deriving in let to
58
3.3 Funes em Haskell
Int
+ Int
Int
Int
Int todosIguais Bool
Int
Exemplos de funes:
Mais exemplos de funes podem ser encontrados em qualquer atividade da vida. Na Figura
3.2 esto mostradas, graficamente, algumas funes baseadas no livro de Simon Thompson [46].
Na Figura, cada desenho do lado esquerdo ou do lado direito um Quadro". Um Quadro o
elemento de entrada da funo que o processa transformando-o em outro Quadro. Por exemplo
a funo espelhaV toma como argumento um Quadro e faz o espelhamento deste Quadro em
relao ao eixo vertical do plano xy.
Neste ponto no ser mostrada uma forma como cada Quadro pode ser implementado, para
ser simulado e processado por um programa. Isto ocorre porque o objetivo aqui apenas mostrar
exemplos de funes. Uma modelagem destes objetos ser mostrada no prximo Captulo.
necessrio salientar a importncia que os tipos dos argumentos e dos resultados tm nas
definies de funes. Eles permitem ao programador estabelecer uma correspondncia bem
definida entre eles e os objetos que modelam, proporcionando uma simulao adequada. Assim,
as funes da Figura reffuncomp tm os tipos:
Um tipo de dado uma coleo de valores onde todos eles tm as mesmas caractersticas. Por
exemplo, os nmeros inteiros, os caracteres, os strings de caracteres, etc. Os tipos das funes,
59
espelhaV
espelhaH
invertecor
escala
sobrepoe
em Haskell, so declaradas por um nome identificador, seguido de ::, vindo em seguida os tipos
de seus argumentos, um a um, com uma flecha ( >) entre eles e, finalmente mais uma flecha
seguida do tipo do resultado que a aplicao da funo produz. Por exemplo, as funes + e
todosIguais, mostradas graficamente na Figura 3.1, tm seus tipos definidos da forma a seguir:
O tipo da funo declarado em sua forma currificada, onde uma funo de n argumentos
considerada como n funes de um nico argumento, da mesma forma adotada pelo -clculo,
vista no Captulo 2.
Os tipos nos do informaes importantes sobre a aplicao das funes. Por exemplo, a
declarao da funo todosIguais, mostrada anteriormente, nos informa que:
Alm dos tipos, a declarao de uma funo exibe explicitamente como o processamento de
seus argumentos deve ser feito. Por exemplo, verifiquemos a definio da funo somaAmbos,
a seguir. A forma como ela processa seus argumentos (entradas), x e y, somando-os, x + y.
Deve ser verificado aqui uma diferena entre a formulao matemtica desta funo e sua
definio, em Haskell. Em Haskell, a definio feita por justaposio dos argumentos, x e y, ao
nome da funo. Na Matemtica, os argumentos so colocados entre parnteses.
Outra forma de definio de funes, em Haskell, declar-las em funo de outras funes,
j definidas. Por exemplo, a funo mmc (mnimo mltiplo comum) entre dois valores inteiros,
60
visto no Captulo 1, pode ser definida em funo das funes mdc (mximo divisor comum) e
div (diviso inteira) entre eles. Vejamos as definies, em Haskell:
Nas definies mostradas, aparecem alguns elementos novos. Por exemplo, na definio da
funo div, deve ser verificada a semelhana entre a definio matemtica e a definio em Haskell.
Em Haskell, as guardas das definies so colocadas em primeiro lugar, enquanto, na Matemtica,
elas aparecem depois da definio. Outro elemento novo a palavra reservada otherwise que
tem o significado de todas as guardas no referidas anteriormente. Apesar destas diferenas,
pode-se verificar que a traduo das definies matemticas para definies em Haskell um
processo direto, inclusive no que se refere ao uso de definio recursiva. Deve ser observado que a
ordem em que as definies so feitas no tem importncia, desde que estejam no mesmo script.
Algumas linguagens funcionais exigem que os tipos das funes sejam declarados explici-
tamente pelo programador. Em Haskell, seus projetistas optaram por permitir que os tipos
das funes sejam inferidos pelo sistema. Isto significa que opcional a declarao dos tipos
das funes pelo programador. Mesmo assim, encoraja-se que esta declarao seja feita ex-
plicitamente, como uma disciplina de programao. Isto permite ao programador um completo
entendimento do problema e da soluo adotada.
61
3.3.3 Casamento de padres (patterns matching)
5. D uma definio para a funo quantosIguais aplicada a trs entradas inteiras e que
retorna quantas delas so iguais.
62
3.4 Tipos de dados em Haskell
Haskell, a exemplo de qualquer linguagem de programao, prov uma coleo de tipos primitivos
e tambm permite tipos estruturados, definidos pelo programador, provendo grande flexibilidade
na modelagem de programas.
Os tipos primitivos de Haskell so: o tipo inteiro (Int ou Integer), o tipo booleano (Bool), o
tipo caractere (Char), o tipo cadeia de caracteres (String) e o tipo ponto flutuante (Float ou
Double) e o tipo lista. Nesta seo, vamos analisar cada um destes tipos primitivos, deixando
o tipo lista para ser tratado no Captulo 4, dada a sua importncia nas linguagens funcionais
e, em particular, em Haskell. Ainda neste Captulo, sero estudados o tipo estruturado produto
cartesiano. Os tipos algbricos e os tipos abstratos de dados sero objeto de estudo em captulos
posteriores.
O tipo inteiro (Int ou Integer)
Como em muitas linguagens, o tipo inteiro primitivo em Haskell. Seu domnio de valores
o mesmo das outras linguagens. Os valores do tipo Integer so representados com o dobro
da quantidade de bits necessrios para representar os valores do tipo Int. Seus operadores
aritmticos e relacionais so os mesmos admitidos na maioria das outras linguagens e esto
mostrados na Tabela 3.4.1.
Exemplo. Vamos construir uma forma de construo de um programa que envolve operaes
com inteiros. Seja uma empresa de vendas que necessita de respostas para as questes a seguir,
para fundamentar suas decises:
Vamos construir algumas funes em Haskell para responder a algumas destas questes,
deixando algumas outras para exerccio do leitor. Para isto necessrio que recordemos as
definies matemticas de funes, vistas no Captulo 1.
63
Questo 1:
Para solucionar a Questo 1, devemos inicialmente construir uma funo venda n que
vai nos informar qual o valor das vendas na semana n. Esta funo pode ser construda de
vrias formas, no entanto ser feita aqui uma definio por casamento de padres, semelhante
definio por enumerao da Matemtica.
Verifiquemos agora como as definies recursivas utilizadas na Matemtica podem ser uti-
lizadas em Haskell. Recordemos a definio matemtica de uma funo usando recurso. Por
exemplo, na definio de uma funo fun, devemos:
Trazendo esta forma de definio para o nosso caso, vamos construir a funo totaldeVendas
n a partir de venda n, que vai mostrar o total de vendas realizadas at a semana n, inclusive.
totaldeVendas :: Int->Int
totaldeVendas n
|n == 0 = venda 0
|otherwise = totaldeVendas (n-1) + venda n
Neste caso, em vez de usarmos padres, sero usadas equaes condicionais (booleanas),
tambm chamadas de guardas", cujos resultados so valores True ou False. As guardas so
avaliadas tambm seqencialmente, da mesma forma feita com o casamento de padres. A
palavra reservada otherwise exerce um papel parecido, mas diferente, do _ (sublinhado), j
descrito anteriormente. A clusula otherwise deve ser utilizada apenas quando houver guardas
anteriores, cujos resultados sejam todos False. Isto significa que se a clusula otherwise for
colocada na primeira definio de uma funo, ocorrer um erro, uma vez que no existem
guardas definidas anteriormente. No caso do _ (sublinhado), esta situao no ocasionar erro.
Usando a definio de venda n e de totaldeVendas n, dadas anteriormente, podemos
calcular a aplicao da funo totaldeVendas 2, da seguinte forma:
64
totaldeVendas n
|n == 0 = venda 0
|n > 0 = totaldeVendas (n - 1) + venda n
|otherwise = 0
Questo 2:
Vamos agora definir a funo maiorVenda, onde maiorVenda n ser igual a venda n,
se a venda mxima ocorreu na semana n. Se a maior venda ocorreu na semana 0, a funo
maiorVenda 0 ser venda 0. Se a maior venda ocorreu at a semana n, pode estar at a
semana (n-1) ou ser venda n.
Esta mesma funo pode ser construda de outra forma, usando uma funo auxiliar, max-
imo, que aplicada a dois inteiros retorna o maior entre eles.
maiorVenda n
|n == 0 = venda 0
|otherwise = maximo (maiorVenda (n - 1)) venda n
1. quadrado x = x * x
2. maximo n m
| n >= m = n (equao condicional)
| otherwise =m
3. usar o valor da funo sobre um valor menor (n - 1) e definir para n.
Exerccios
1. Defina uma funo para encontrar a semana em que ocorreu a venda mxima entre a
semana 0 e a semana n. O que sua funo faz se houver mais de uma semana com vendas
mximas?
2. Defina uma funo para encontrar uma semana sem vendas entre as semanas 0 e n. Se no
existir tal semana, o resultado deve ser n + 1.
3. Defina uma funo que retorne o nmero de semanas sem vendas (se houver alguma).
4. Defina uma funo que retorna o nmero de semanas nas quais foram vendidas s unidades,
para um inteiro s 0. Como voc usaria esta soluo para resolver o problema 3?
65
5. Teste as funes que usam vendas com a definio vendas n = n mod 2 + (n + 1) mod
3
6. D uma definio da funo fat que calcula o fatorial de n, onde n um inteiro positivo.
8. D uma definio de uma funo que retorne i-simo nmero da sequncia de Fibonacci (0,
1, 1, 2...).
66
Tabela 3.5: Principais tipos numricos em Haskell.
Tipo Descrio
Double Ponto flutuante de preciso dupla
Float Ponto flutuante de preciso simples
Int Inteiro sinalizado de preciso fixa (229 ..229 1)
Int8 Inteiro sinalizado de 8 bits
Int16 Inteiro sinalizado de 16 bits
Int32 Inteiro sinalizado de 32 bits
Int64 Inteiro sinalizado de 64 bits
Integer Inteiro sinalizado de preciso arbitrria
Rational Nmeros racionais de preciso arbitrria
Word Inteiro no sinalizado de preciso fixa
Word8 Inteiro no sinalizado de 8 bits
Word16 Inteiro no sinalizado de 16 bits
Word32 Inteiro no sinalizado de 32 bits
Word64 Inteiro no sinalizado de 64 bits
Exerccios
1. D a definio de uma funo nAnd :: Bool > Bool > Bool que d o resultado
True, exceto quando seus dois argumentos so ambos True.
2. Defina uma funo numEquallMax :: Int > Int > Int > Int onde numEquall-
Max n m p retorna a quantidade de nmeros iguais ao mximo entre n, m e p.
3. Como voc simplificaria a funo
funny x y z
|x > z = True
|y >= x = False
|otherwise = True
67
O tipo caractere (Char)
Os caracteres em Haskell so literais escritos entre aspas simples. Existem alguns caracteres
que so especiais por terem utilizaes especficas. Entre eles se encontram:
\t - tabulao \ - aspas simples
\n - nova linha \ - aspas duplas
\\ - uma barra invertida \34 - ?
Existem algumas funes pr-definidas em Haskell feitas para converter caracteres em nmeros
e vice-versa.
Exerccio
Defina uma funo mediadasVendas :: Int > Float que d como resultado a mdia
aritmtica entre os valores de vendas 0 at vendas n.
O tipo cadeia de caracteres (String)
O tipo cadeia de caracteres, normalmente chamado de String, tem uma caracterstica pecu-
liar em Haskell. Apesar deste caso ser considerado um caso patolgico por alguns pesquisadores
de linguagens de programao, os criadores de Haskell admitiram duas formas para este tipo.
Ele um tipo pr-definido como String, mas tambm pode ser considerado como uma lista de
caracteres. Para satisfazer as duas formas, as strings podem ser escritas entre aspas duplas ou
usando a notao de lista de caracteres (entre aspas simples). Por exemplo, "Constantino"tem
o mesmo significado que [C, o, n, s, t, a, n, t, i, n, o], em Haskell.
Todas as funes sobre listas do Prelude.hs podem ser aplicadas a Strings, uma vez que elas
so definidas sobre listas de algum tipo e uma string tambm uma lista.
Toda aplicao de funo, em Haskell, produz um resultado. Podemos verificar isto atravs
da tipificao das funes. Ocorre, no entanto, que para mostrar um resultado, no monitor
ou em outro dispositivo de sada, necessrio definir uma funo para esta tarefa. E qual
deve ser o resultado desta operao? Mostrar um valor no monitor no implica em retorno
de qualquer valor como resultado. Algumas linguagens de programao funcional, como ML,
resolvem este problema de comunicao com o mundo exterior atravs de atribuies destrutivas,
o que descaracteriza a linguagem como funcional pura, transformando-a em impura.
No entanto, Haskell foi projetada como uma linguagem funcional pura e, para resolver este
tipo de comunicao, adota uma semntica de aes baseada em Mnadas, uma teoria bastante
complexa. Uma ao em Haskell um tipo de funo que retorna um valor do tipo IO (),
para ser coerente com o projeto da linguagem. Mostraremos aqui algumas funes usadas na
comunicao com o mundo exterior e formas de aplicao para facilitar o entendimento pelo
leitor. Caso contrrio, seria bastante tedioso usar funes sem poder verificar os resultados de
suas aplicaes.
A primeira destas funes pr-definidas em Haskell putStr :: String > IO () que
utilizada para mostrar strings no monitor. Assim,
Neste caso, as strings podem ser mostradas na tela do monitor. E se um valor no for uma
string? Neste caso, a soluo transformar o valor em uma String e usar a funo putStr. Para
esta misso, foi definida a funo show :: t > String que transforma um valor, de qualquer
68
tipo, em uma string. Alm destas, a funo read :: String > t toma uma string como
argumento de entrada e a transforma em um valor. Por exemplo,
Exerccios:
1. Defina uma funo para converter letras minsculas em maisculas e que retorne o prprio
caractere se a entrada no for um caractere minsculo.
2. Defina uma funo charParaInt :: Char > Int que converte um dgito em seu valor
(por exemplo, 8 em 8). O valor de um caractere no dgito deve ser 0 (zero).
3. Defina uma funo imprimeDigito :: Char > String que converte um dgito em sua
representao em portugus. Por exemplo, 5 deve retornar cinco".
4. Defina uma funo romanoParaString :: Char > String que converte um algarismo
romano em sua representao em Portugus. Por exemplo, romanoParaString V =
cinco".
5. Defina uma funo emTresLinhas :: String > String > String > String que
toma trs strings e retorna um nico string mostrando os trs strings em linhas separadas.
6. Defina uma funo replica :: String > Int > String que toma um String e um
nmero natural n e retorna n cpias da String, todas juntas. Se n for 0, o resultado deve
ser a String vazia (), se n for 1, retorna a prpria String.
Programao top-down
Vamos nos referir novamente ao problema das vendas, descrito anteriormente. Suponhamos que
as quantidades de vendas sejam as mostradas na Tabela 3.7.
69
imprimeTab :: Int -> String
imprimeTab n = putStr
(cabecalho ++ imprimeSemanas n ++ imprimeTotal ++ imprimeMedia n)
Agora necessrio que estas funes componentes sejam definidas. Por exemplo, a funo
cabecalho formada apenas por uma string para representar os ttulos dos tens da Tabela 3.7.
cabecalho :: String
cabecalho
= "___________________\n|| Semana | Vendas ||\n___________________\n"
A funo imprimeTotal utilizada apenas para imprimir o total de todas as vendas ocorridas
em todas as semanas. Sua definio a seguinte:
imprimeTotal :: String
imprimeTotal ="||__________________||_\n|| Total |"++show ((vendas 0)+
(vendas 1)+(vendas 2)+(vendas 3))++" ||\n___________________\n"
Fase de combinao. Esta fase consiste na combinao das solues dos sub-problemas
em uma soluo nica.
Programao bottom-up
70
Vamos considerar como exemplo a computao do n-simo nmero da seqncia de Fibonacci.
Utilizando a tcnica top-down, esta funo pode ser definida da seguinte forma:
Uma representao grfica das chamadas recursivas na execuo desta funo aplicada a 4,
est mostrada na Figura 3.3. Pode ser observado que fib 2 computada duas vezes, fib 1
computada trs vezes e fib 0 computada duas vezes.
fib 4
fib 3 fib 2
fib 1 fib 0
J a Figura 3.4 mostra a execuo de fib 4, onde fib definida utilizando uma tcnica
bottom-up, onde os nmeros calculados so reutilizados nas computaes seguintes. Uma relao
de recorrncia que descreve como computar grandes instncias a partir de menores deve ser
associada com cada aplicao. Esta relao deve se basear em um princpio conhecido como
princpio da otimalidade" que estabelece, informalmente, que uma soluo tima se ela puder
ser construda usando sub-solues timas.
fib 0
fib 1
fib 2
fib 3
fib 4
A programao top-down parte de um caso geral para casos especficos, enquanto a modelagem
bottom-up tem o sentido inverso desta orientao. Ela inicia com as definies mais particulares,
para depois comp-las em uma forma mais geral. Para exemplificarmos esta situao, vamos
redefinir a funo imprimeSemana utilizando a funo rJustify, que por sua vez usa a funo
imprimeespacos para preencher os espaos em branco necessrios. A funo rJustify tal que
rJustify 10 Maria"= Maria".
71
imprimeespacos n = " "++ imprimeespacos n-1
1. D uma definio de uma funo tabeladeFatoriais :: Int > Int > String que
mostre, em forma de tabela, os fatoriais dos inteiros de m at n, inclusive de ambos.
Haskell tambm admite a possibilidade de que o usurio construa seus prprios tipos de da-
dos, de acordo com as necessidades que ele tenha de simular problemas do mundo real. Os
tipos estruturados so construdos a partir de outros tipos, primitivos ou estruturados. Esta
uma caracterstica muito importante desta linguagem, por facilitar a vida dos programadores,
permitindo um grau muito maior de abstrao dos problemas a serem resolvidos.
O produto cartesiano representado em Haskell pelas tuplas, que podem ser duplas, triplas,
qudruplas, etc. Na maioria das linguagens de programao imperativas, este tipo de dados
implementado atravs de registros ou estruturas. Em Haskell, o tipo (t1 , t2 , ..., tn ) consiste de
n-uplas de valores (v1 , v2 , ..., vn ) onde v1 ::t1 , v2 ::t2 , ..., vn ::tn .
Por exemplo,
As funes sobre tuplas, apesar de poderem ser definidas de vrias formas, so comumente
definidas por pattern matching.
72
somaPar :: (Int, Int) -> Int
somaPar (x, y) = x + y
Podem ser definidas funes para mostrar casos particulares de uma tupla:
nome (n, p, a) = n
fone (n, p, a) = p
idade (n, p, a) = a
somaPar :: (Int, Int) -> Int e somaDois :: Int -> Int -> Int
somaPar (a, b) = a + b somaDois a b = a + b
Apesar dos resultados das aplicaes das funes somaPar e somaDois serem os mesmos,
elas so distintas. A funo somaPar requer apenas um argumento, neste caso uma tupla,
enquanto a funo somaDois requer dois argumentos do tipo inteiro.
3.4.4 Escopo
O escopo de uma definio a parte de um programa na qual ela visvel e portanto pode ser
usada. Em Haskell, o escopo das definies todo o script, ou seja, todo o arquivo no qual a
definio foi feita. Por exemplo, vejamos a definio de ehImpar n, a seguir, que menciona a
funo ehPar, apesar desta ser definida depois. Isto s possvel porque elas compartilham o
mesmo escopo.
ehPar 0 = True
ehPar n = ehImpar (n-1)
Definies locais
Haskell permite definies locais atravs da palavra reservada where. Por exemplo,
73
As definies locais podem incluir outras definies de funes e podem usar definies locais
a uma expresso, usando a palavra reservada let.
let x = 3 + 2; y = 5 - 1 in x2 + 2*x*y - y
As definies locais so visveis apenas na equao onde elas foram declaradas. As variveis
que aparecem do lado esquerdo da igualdade tambm podem ser usadas em definies locais, do
lado esquerdo. Por exemplo,
maximoQuadrado x y
|quadx > quady = quadx
|otherwise = quady
where
quadx = quad x
quady = quad y
quad :: Int -> Int
quad z = z * z
As definies locais podem ser usadas antes delas serem definidas e tambm podem ser usadas
em resultados, em guardas ou em outras definies locais. Como exemplo,
onde a funo quantosIguaisValor pode ser definida de uma das formas mostradas na Tabela
3.8.
74
tiver valor falso, a expresso <exp2> escolhida para ser avaliada e seu resultado ser o da
expresso.
A exemplo de muitas linguagens de programao, Haskell tambm suporta a construo
case, utilizada quando mltiplos valores existem como resultado da avaliao de uma expresso
e, dependendo de cada um deles uma expresso escolhida para ser avaliada. Como exemplo,
uma funo fun que retorna um valor dependendo de sua entrada pode ser definida por:
fun x = case x of
0 -> 50
1 -> 100
2 -> 150
3 -> 200
_ -> 500
3.4.6 Clculos:
A avaliao usada por Haskell chamada de avaliao preguiosa, onde cada expresso avaliada
apenas uma vez e se necessrio. Um clculo s realizado se for realmente necessrio e seu valor
colocado em uma clula da heap. Se ele for novamente solicitado, j est calculado e pronto
para ser utilizado. Por este motivo, Haskell, a exemplo de qualquer linguagem funcional e todas
as modernas linguagens de programao, fazem o gerenciamento dinmico de memria de forma
automtica, conhecido como Garbage Collection ou Coleta de lixo. Para mais detalhes sobre este
tema o leitor deve consultar as referncias [40, 41, 42, 43, 44].
Vamos mostrar, detalhadamente, a seqncia de avaliaes da aplicao de duas funes j
definidas anteriormente, somaquadrados e maximasOcorrencias.
75
ehvalor 1 = if 1 == 2 then 1 else 0
= if False then 1 else 0
= 0
= 1 + 0 + ehvalor 2
where
ehvalor 2 = if 2 == 2 then 1 else 0
= if True then 1 else 0
= 1
= 1 + 0 + 1
= 2
= (2, 2)
Esta seqncia de clculos deve ser acompanhada detalhadamente pelo leitor para entender
a forma como o compilador (ou interpretador) Haskell executa seus clculos. Este entendimento
tem importncia fundamental na construo de funes, notadamente de funes que tratam
com listas potencialmente infinitas, a serem vistas mais adiante.
Exerccios:
2. Defina uma funo cJustify :: Int > String > String onde cJustify n st retorna
uma string de tamanho n, adicionando espaos antes e depois de st para centraliz-la.
3. Defina uma funo stars :: Int > String de forma que stars 3 retorna ***. Como
deve ser tratada uma entrada negativa?
76
show b ++ "*x + " ++ show c ++ " = 0.0" ++ "\n\ntem "
Na equao do segundo grau, se a entrada para o coeficiente a for zero, no ser possvel
a diviso de qualquer nmero por ele. Neste caso, o programa deve abortar a execuo e uma
exceo deve ser feita, descrevendo o motivo. A funo umaRaiz deve ser re-definida da seguinte
forma:
umaRaiz a b c
|(a /= 0.0) = -b/ (2.0 * a)
|otherwise = error "umaRaiz chamada com a == 0"
Uma soluo para um caso menor pode ser utilizada para um caso maior.
Se uma expresso aparecer mais de uma vez, forte candidata a ser declarada como uma
funo.
Exerccios
Para os exerccios a seguir, considere os pontos do plano como sendo do tipo Ponto =
(Float, Float). As linhas do plano so definidas por seus pontos inicial e final e tm o tipo
Linha = (Ponto, Ponto).
77
2. Defina uma funo que retorne a norma de um vetor dado por suas coordenadas.
3. Se uma linha determinada pelos pontos (x1, y1) e (x2, y2), sua equao definida por
(y - y1)/(x - x1) = (y2 - y1)/(x2 - x1). Defina uma funo do tipo valorY :: Float >
Linha > Float que retorna a ordenada y do ponto (x,y), sendo dados x e uma linha.
Uma prova uma argumentao lgica ou matemtica para verificar se alguma premissa ou
no vlida, em quaisquer circunstncias.
Este tema tem importncia fundamental na construo de programas, uma vez que deve-se
buscar a garantia de que o programa esteja correto e que ele realiza apenas a ao para a qual foi
criado. A prova de programas aumenta sua importncia a cada dia, uma vez que os problemas
esto se tornando cada vez mais complexos e desafiadores. Por exemplo, problemas nucleares ou
outros que envolvam vidas humanas podendo colocar em jogo suas sobrevivncias. Para estes
casos, h de existir uma prova de que ele produz a soluo correta.
No entanto, existe um dilema da parte dos usurios que, s vezes, tm dificuldades de realizar
provas de programas, achando ser uma tcnica de fundamentao matemtica e trabalhosa. A
prova de programas em uma linguagem imperativa realmente tediosa, no entanto, como ser
visto, ela bem menos difcil em uma linguagem funcional como Haskell.
Existem, em Haskell, trs maneiras de realizar provas: a prova direta, a prova por casos e a
prova por induo matemtica. Vamos analis-las atravs de exemplos, lembrando ao leitor que
o domnio dessas tcnicas s conseguido atravs da prtica.
Provas diretas
A prova direta feita aplicando as definies das funes. Por exemplo, sejam as funes troca,
cicla e recicla, definidas da seguinte forma:
A partir destas definies, podemos provar assertivas nas quais estejam envolvidas. Por
exemplo, podemos provar que troca (troca (a, b)) = (a, b), ou ainda que cicla (recicla (a,
b, c)) = recicla (cicla (a, b, c)). Vejamos como isto pode ser feito:
78
Provas por casos
Exerccios
1. Prove que cicla (cicla (cicla (a, b, c))) = (a, b, c) para todo a, b e c.
3. Dada a definio
trocaSe :: (Int, Int) > (Int, Int)
trocaSe (a, b)
| a <= b = (a, b)
| otherwise = (b, a)
Prove que para todo a e b definidos, trocaSe(trocaSe(a, b)) = trocaSe(a, b).
Induo matemtica
Da Matemtica, sabemos que o esquema de prova por induo dentro do conjunto dos nmeros
naturais uma forma muito comum. Um sistema similar pode ser utilizado para provar progra-
mas codificados em Haskell.
Para provar que uma propriedade P(n) vlida para todo natural n, deve-se:
Passo indutivo: Para n > 0, provar P(n), assumindo que P(n-1) vlida.
79
Caso base (P(0)): fatorial 0 = 1 (por fat 1) e 1 > 0. Logo fatorial 0 > 0, significando que
a propriedade vlida para o caso base.
Passo indutivo (P(n)): fatorial n = n * fatorial (n-1), (por fat 2) admitindo-se que n>0. A
hiptese de induo informa que fatorial (n-1) > 0, ou seja, a propriedade P vlida para
n-1. Assim o fatorial de n o produto de dois fatores sendo ambos maiores que zero. O
produto de dois nmeros positivos tambm positivo. Logo, maior que 0.
Concluso: como a propriedade P vlida para o caso base e para o passo indutivo, logo
vlida para todo n natural.
Enquanto a induo formula provas para P(0), P(1), ..., a definio recursiva de fatorial, vista
anteriormente, constri resultados para fatorial 0, fatorial 1, .... A forma como P(n-1)
assumida para se provar P(n) semelhante forma usada por fatorial (n-1) para encontrar o
valor de fatorial (n).
Este esquema de prova normalmente aplicado a funes definidas por recurso primitiva
representando to somente um processo de traduo semelhante ao esquema de prova por induo
matemtica.
Simon Thompson escreveu um guia de passos a serem seguidos nos esquemas de provas por
induo em Haskell [46]. A seqncia de passos de provas proposta por ele instrutiva e serve
de roteiro, principalmente para quem est dando os primeiros passos em direo a este estudo.
Com o decorrer do tempo, este esquema passa a ser um processo automtico.
Estgio 0: escrever o objeto da prova em portugus,
Estgio 1: escrever o objeto da prova em linguagem formal,
Estgio 2: escrever os sub-objetos da prova por induo:
P(0):
P(n), para todo n>0, assumindo P(n-1)
Estgio 3: Provar P(0)
Estgio 4: Provar P(n), para n>0, lembrando que deve e pode usar P(n-1)
Exemplo: Sejam as definies das funes a seguir:
80
Estgio 0: provar que a soma das potncias de 2 de 0 a n, adicionada a 1
igual a (n + 1)-sima potncia de 2.
Estgio 1: provar P(n): sumPowers n + 1 = power2 (n+1)
Estgio 2: sumPowers 0 + 1 = = power2 (0 + 1), para n = 0?
sumPowers n + 1 = = power2 (n + 1), para n > 0?,
assumindo que sumPowers (n - 1) + 1 = power2 n
Estgio 3: sumPowers 0 + 1 = 1 + 1 = 2 por (3)
power2 (0 + 1) = 2 * power2 0 = 2 * 1 = 2 por (2)
logo, a prova vlida para o caso base.
2. Prove que, para todo nmero natural n, fib (n+1) power2 (n div 2).
3.6 Resumo
Neste Captulo, foi dado incio ao estudo de programao em Haskell. Foram vistos os tipos de
dados primitivos e as tuplas. Alguns exerccios foram resolvidos para dar uma noo ao usurio
da potencialidade da linguagem e outros foram deixados para o leitor.
O livro de Simon Thompson [46] foi a fonte mais utilizada para este estudo, por ser um livro
que apresenta um contedo terico bem estruturado e fundamentado, alm de muitos exerccios
resolvidos e muitos problemas propostos.
O livro de Richard Bird [4] outra fonte importante de exerccios resolvidos e propostos,
apesar de sua seqncia de abordagens seguir uma ordem distinta, exigindo um conhecimento
anterior sobre programao funcional, o que o torna mais defcil de ser seguido por iniciantes
neste tema.
Outra referncia importante o livro de Paul Hudak [11] que apresenta a programao
funcional em Haskell com exemplos aplicados Multimdia, envolvendo a construo de um
editor grfico e de um sistema utilizado em msica, entre outros. Para quem imagina que
Haskell s aplicado a problemas da Matemtica, esta referncia pe por terra este argumento.
81
82
Captulo 4
O tipo Lista
4.1 Introdio
Lista o tipo de dado mais importante nas linguagens funcionais. Todas as linguagens funcionais
a implementam como um tipo primitivo, juntamente com uma gama imensa de funes para a
sua manipulao. A notao utilizada para as listas colocar seus elementos entre colchetes.
Por exemplo, [1,2,3,4,1,3] uma lista de inteiros, [True,False] uma lista de booleanos, [a,
a, b] uma lista de caracteres e ["Marta", "Marlene"] uma lista de strings. H, no
entanto, que se diferenciar as listas homogneas, que so as listas onde todos os valores so do
mesmo tipo, das listas heterogneas, onde os componentes podem ter mais de um tipo. Haskell
s admite listas homogneas. Por exemplo, [False, 2,"Maria"] no uma lista em Haskell, por
ser heterognea. Em compensao, podemos ter a lista [totalVendas, totalVendas], que tem
o tipo [Int > Int] como tambm a lista [[12,1], [3,4], [4,4,4,4,4], [ ]] que tem o tipo [[Int]],
uma lista de listas de inteiros, alm de outras possibilidades.
Existem duas formas como as listas podem se apresentar:
a lista vazia, simbolizada por [ ], que pode ser de qualquer tipo. Por exemplo, ela pode
ser de inteiros, de booleanos, etc. Dito de outra forma, [ ] do tipo [Int] ou [Bool] ou
[Int > Int], significando que a lista vazia est na interseo de todas as listas, sendo o
nico elemento deste conjunto.
a lista no vazia, simbolizada por (a : x), onde a representa um elemento da lista,
portanto tem um tipo, e x representa uma lista composta de elementos do mesmo tipo de
a. O elemento a chamado de cabea e x a cauda da lista.
A ordem em uma lista importante, ou seja, [1,3] /= [3,1] e [False] /= [False, False].
A lista [m .. n] igual lista [m, m+1, ..., n]. Por exemplo, [1 .. 5] = [1, 2, 3, 4, 5].
A lista [3.1 .. 7.0] = [3.1, 4.1, 5.1, 6.1].
83
A lista [m,p .. n] igual lista de m at n em passos de p-m. Por exemplo, [7,6 ..3] =
[7, 6, 5, 4, 3] e [0.0, 0.3 ..1.0] = [0.0, 0.3, 0.6, 0.9].
A lista vazia no tem cabea e nem cauda. Se tivesse qualquer destes dois componentes,
no seria vazia.
A lista no vazia tem cabea e cauda, onde a cauda tambm uma lista, que pode ser
vazia, ou no.
A funo somaLista toma como argumento uma lista de inteiros e retorna, como resultado,
um valor que a soma de todos os elementos da lista argumento. Se a lista for vazia ([ ]), a soma
ser 0. Se a lista no for vazia (a : x), o resultado ser a soma de sua cabea (a) com o resultado
da aplicao da mesma funo somaLista cauda da lista (x). Esta definio recursiva, uma
caracterstica muito utilizada, por ser a forma usada para fazer iterao em programas funcionais.
Devemos observar que a ordem em que os padres so colocados tem importncia fundamental.
No caso em voga, primeiramente foi feita a definio para a lista vazia e depois para a lista no
vazia. Dependendo da necessidade do programador, esta ordem pode ser invertida.
Vejamos a seqncia de clculos da aplicao da funo somaLista lista [2, 3, 5, 7].
O construtor de listas, chamado de cons e sinalizado por : (dois pontos), tem importncia
fundamental na construo de listas. Ele um operador que toma como argumentos um elemento,
de um tipo, e uma lista de elementos deste mesmo tipo e insere este elemento como a cabea da
nova lista. Por exemplo,
10 : [ ] = [10]
2 : 1 : 3 : [ ] = 2 : 1 : [3] = 2 : [1,3] = [2,1,3]
84
significando que cons (:) associa seus componentes pela direita, ou seja: a : b : c = a : (b : c)/ =
(a : b) : c
Mas qual o tipo de cons? Observando que 4 : [3] = [4, 3], ento cons tem o tipo:
No entanto, verificamos tambm que True : [False] = [True, False]. Agora cons tem o
tipo:
Isto mostra que o operador cons polimrfico. Desta forma, seu tipo :
onde [t] uma lista de valores, de qualquer tipo, desde que seja homognea.
85
Alm das funes pr-definidas, o usurio tambm pode construir funes para manipular
listas. Aqui a criatividade o limite. Vamos mostrar isto atravs de um exemplo simples e depois
atravs de um exemplo mais complexo.
Vamos construir uma funo que verifica se um determinado elemento pertence, ou no, a
uma lista. Para isto vamos construir a funo pertence:
Esta mesma funo tambm pode ser codificada de outra forma, usando guardas:
pertence b [ ] = False
pertence b (a:x)
|b == a = True
|otherwise = pertence x b
Modelagem. Voltemos nossa ateno agora para os exemplos das funes espelhaH e espel-
haV, vistas no Captulo anterior. Suponhamos que um cavalo seja um objeto do tipo Quadro.
Um Quadro pode ser modelado por uma matriz (12x12), de caracteres. Desta forma, um cavalo
uma lista de 12 linhas e cada linha uma lista de 12 caracteres, ou seja, um cavalo uma lista
de listas de caracteres, conforme pode ser visto graficamente na representao a seguir, onde os
pontos esto colocados apenas para facilitar a contagem, ou seja, eles esto colocados apenas
para representar os caracteres em branco. Os resultados das aplicaes das funes espelhaH e
espelhaV a um cavalo tambm esto mostrados na mesma Figura, a seguir.
As funes espelhaH e espelhaV tambm podem ser declaradas a partir de outras funes
j definidas para outras finalidades, o que proporciona ainda mais flexibilidade ao programador.
Por exemplo,
86
A funo reverse, quando aplicada a uma lista de elementos de qualquer tipo produz, como
resultado, uma outra lista com os mesmos elementos da primeira lista, na ordem inversa. Por
exemplo, reverse [1,2,3] = [3,2,1] e reverse [a, b, c] = [c, b, a]. Isto significa
que a funo reverse aplicada a cada linha do cavalo, que uma lista de caracteres, d, como
resultado, a mesma linha mas com a ordem de seus caracteres invertida. A funo map toma
a funo reverse, o primeiro de seus dois argumentos, e a aplica a cada uma das linhas de seu
segundo argumento (um cavalo). As funes map e reverse so pr-definidas em Haskell e elas
sero objeto de estudo mais profundo nas prximas sees.
Algumas concluses importantes podem ser tiradas a partir destes exemplos:
a funo reverse pode ser aplicada a uma lista de valores de qualquer tipo. Isto significa
que uma mesma funo pode ser aplicada a mais de um tipo de dados. Isto significa
polimorfismo, ou seja, a utilizao genrica de uma funo, aumentando a produtividade
de software.
Um argumento da funo map reverse, uma funo. Isto significa que uma funo pode
ser passada como parmetro para uma outra funo. Neste caso, diz-se que as funes so
consideradas como cidados de primeira categoria, permitindo-se a elas os mesmos direitos
que qualquer outro tipo de dado.
O resultado da aplicao da funo map funo reverse uma outra funo que
aplicada a um outro argumento que, neste caso, uma lista. Neste caso, diz-se que, nas
linguagens funcionais, as funes so de alta ordem, ou seja, podem ser passadas como
argumentos de uma funo e tambm podem retornar como resultados da aplicao de
uma funo.
Exerccios
4. Defina a funo product :: [Int] > Int que retorna o produto de uma lista de inteiros.
5. Defina a funo and :: [Bool] > Bool que retorna a conjuno da lista. Por exemplo,
and [e1 , e2 , . . . , en ] = e1 &&e2 && . . . &&en .
6. Defina a funo concat :: [[Int]] > [Int] que concatena uma lista de listas de inteiros
transformando-a em uma lista de inteiros. Por exemplo, concat [[3,4], [2], [4,10]] =
[3,4,2,4,10].
Vamos agora mostrar um exemplo mais complexo, envolvendo a ordenao de uma lista de
inteiros.
Uma forma de ordenar uma lista no vazia inserir a cabea da lista no local correto, que
pode ser na cabea da lista ou pode ser na cauda j ordenada. Por exemplo, para ordenar a
87
lista de inteiros [3,4,1], devemos inserir 3 na cauda da lista, j ordenada, ou seja em [1,4]. Para
que esta cauda j esteje ordenada necessrio apenas chamar a mesma funo de ordenao,
recursivamente, para ela. Vamos definir uma funo de ordenao, ordena, que utiliza uma
funo auxiliar insere, cuja tarefa inserir cada elemento da lista no lugar correto.
A lista vazia considerada ordenada por vacuidade. Por isto a primeira definio. Para o
caso da lista no vazia, devemos inserir a cabea (a) na cauda j ordenada (ordena x). Falta
apenas definir a funo insere, que auto-explicativa.
Este mtodo de ordenao conhecido como insero direta e o leitor deve observar a sim-
plicidade como ele implementado em Haskell. Sugerimos comparar esta implementao com
outra, em qualquer linguagem convencional. Alm disso, tambm se deve considerar a possibil-
idade de aplicao desta mesma definio listas de vrios tipos de dados, significando que a
definio pode ser polimrfica. Isto significa que, na definio da funo insere, a nica operao
exigida sobre os valores dos elementos da lista a ser ordenada que eles possam ser comparados
atravs da operao . Uma lista de valores, de qualquer tipo de dados, onde esta operao seja
possvel entre estes valores, pode ser ordenada usando esta definio.
Exerccios
2. Defina uma funo numOcorre :: [t] > t > Int, onde numOcorre l s retorna o
nmero de vezes que o tem s aparece na lista l.
4. Defina a funo ocorreUmaVez :: [Int] > [Int] que retorna a lista de nmeros que
ocorrem exatamente uma vez em uma lista. Por exemplo, ocorreUmaVez [2,4,2,1,4] =
[1].
O casamento de padres j foi analisado anteriormente, mas sem qualquer profundidade e for-
malismo. Agora ele ser visto com uma nova roupagem, apesar de usar conceitos j conhecidos.
Os padres em Haskell so dados por:
88
um padro de tuplas (p1 , p2 , ..., pk ). Um argumento para casar com este padro tem de ser
da forma (v1 , v2 , ..., vk ), onde cada vi deve ser do tipo pi .
Nas linguagens funcionais, a forma usada para verificar se um padro casa, ou no, com um
valor realizada da seguinte maneira:
1. ela tem cabea e tem cauda, portanto uma lista correta. Portanto,
Pode-se perguntar: em que situaes um argumento a casa com um padro p? Esta questo
respondida atravs da seguinte lista de clusulas:
se p for uma tupla de padres (p1 , p2 , ..., pk ), a casa com p se a for uma tupla (a1 , a2 , ..., ak )
e se cada ai casar com cada pi .
se p for uma lista de padres (p1 : p2 ), a casa com p se a for uma lista no vazia. Neste
caso, a cabea de a associada com p1 e a cauda de a associada com p2 .
se p for um sublinhado (_), a casa com p, mas nenhuma associao feita. O sublinhado
age como se fosse um teste.
Se os argumentos de zip forem duas listas, ambas no vazias, forma-se a tupla com as cabeas
das duas listas, que ser incorporada lista de tuplas resultante e o processo continua com a
aplicao recursiva de zip s caudas das listas argumentos. Este processo continua at que o
padro de duas listas no vazias falhe. No momento em que uma das listas, ou ambas, for vazia,
o resultado ser a lista vazia e a execuo da funo termina. Vamos verificar o resultado de
algumas aplicaes.
Exerccios
1. Defina uma funo somaTriplas que soma os elementos de uma lista de triplas de nmeros,
(c, d, e).
89
2. Defina uma funo somaPar que d como resultado a lista das somas dos elementos de
uma lista de pares de nmeros.
4. Defina uma funo unzip :: [(Int, Int)] > ([Int], [Int]) que transforma uma lista de
pares em um par de listas. Sugesto: defina antes as funes unZipLeft, unZipRight ::
[(Int, Int)] > [Int], onde unZipLeft [(2,4), (3,5), (4,78)] = [2,3,4] e unZipRight
[(2,4), (3,5), (4,78)] = [4,5,78].
Exemplo: Agora vamos analisar um exemplo mais detalhado da aplicao de listas em Haskell,
baseado em Simon Thompson [46]. Seja um banco de dados definido para contabilizar as retiradas
de livros de uma Biblioteca, por vrias pessoas. Para simular esta situao, vamos construir uma
lista de tuplas compostas pelo nome da pessoa que tomou emprestado um livro e do ttulo do
livro. Para isto, teremos:
Vamos construir uma lista fictcia para servir apenas de teste, ou seja, vamos supor que, em
um determinado momento, a lista esteja composta das seguintes tuplas:
teste = [("Paulo", "A Mente Nova do Rei"), ("Ana", "O Segredo de Luiza"),
("Paulo", "O Pequeno Principe"), ("Mauro", "O Capital"),
("Francisco", "O Auto da Compadecida")]
1. Operaes de consulta:
Uma funo que informa os livros que uma determinada pessoa tomou emprestado.
Uma funo que informa todas as pessoas que tomaram emprestado um determinado
livro.
Uma funo que informa se um determinado livro est ou no emprestado.
Uma funo que informa a quantidade de livros que uma determinada pessoa tomou
emprestado.
2. Operaes de atualizao:
Inicialmente, vamos construir a funo livrosEmprestados que pode ser utilizada para
servir de roteiro para a definio das outras funes de consulta, deixadas, como exerccio, para
o leitor.
90
Vamos agora definir as funes de atualizao:
Que motivos o leitor imagina que o programador tenha levado em conta na definio da
funo devolveLivro, a exemplo da funo zip, definida anteriormente, preferindo apresentar a
definio para o padro de lista vazia aps a definio para o padro de lista no vazia, quando
o normal seria apresentar estes padres na ordem inversa?
Exerccio:
Modifique o banco de dados da Biblioteca anterior e as funes de acesso, de forma que:
exista um nmero mximo de livros que uma pessoa possa tomar emprestado,
exista uma lista de palavras-chave associadas a cada livro, de forma que cada livro possa
ser encontrado atravs das palavras-chave a ele associadas, e
existam datas associadas aos emprstimos, para poder detectar os livros com datas de
emprstimos vencidas.
Desta forma ex1 = [4,8,14]. Se quizermos encontrar a lista ex2 composta dos elementos de ex
que sejam pares, podemos declarar
91
Propriedades de uma expresso ZF
Os geradores podem ser combinados com nenhuma, uma ou mais expresses booleanas.
Sendo ex a lista do Exemplo anterior, ento
O algoritmo quicksort
Na seo 4.1 foi mostrado como o algoritmo de ordenao por insero direta pode ser imple-
mentado em Haskell. Aqui ser mostrada a codificao de um outro algoritmo de ordenao,
quicksort, destacando a simplicidade como ela feita. Suponhamos que o algoritmo quicksort
seja aplicado a uma lista de inteiros, ressaltando que ele tambm pode ser aplicado a listas de
qualquer tipo de dados, desde que estes dados possam ser comparados pelas relaes de ordem:
maior, menor e igual.
O algoritmo quicksort utiliza o mtodo de diviso e conquista em seu desenvolvimento. Em
sua descrio, escolhe-se um elemento, o pivot, e a lista a ser ordenada dividida em duas sub-
listas: uma contendo os elementos menores ou iguais ao pivot e a outra contendo os elementos
da lista que sejam maiores que o pivot. Neste ponto, o algoritmo aplicado recursivamente
primeira e segunda sub-listas, concatenando seus resultados, com o pivot entre elas. A escolha
do pivot, normalmente, feita pelo elemento do meio da lista, na expectativa de que ele esteja
prximo da mdia da amostra. No entanto, esta escolha apenas estatstica e, na realidade,
pode-se escolher qualquer elemento da lista. Em nossa implementao do quicksort em Haskell,
escolhemos como pivot a cabea da lista, por ser o elemento mais fcil de ser obtido.
Vamos acompanhar a seqncia de operaes na aplicao do quicksort lista [4,3,5,10].
quicksort [4,3,5,10]
= quicksort [3] ++ [4] ++ quicksort [5,10]
= (quicksort [ ] ++ [3] ++ quicksort [ ]) ++ [4] ++
(quicksort [ ] ++ [5] ++ quicksort [10])
= ([ ] ++ [3] ++ [ ]) ++ [4] ++ ([ ] ++ [5] ++
(quicsort [ ] ++ [10] ++ quicsort [ ]))
= [3] ++ [4] ++ ([5] ++ ([ ] ++ [10] ++ [ ]))
= [3,4] ++ ([5] ++ [10])
= [3,4] ++ [5,10]
= [3,4,5,10]
92
Agora vamos definir formalmente o quicksort, usando expresses ZF.
Esta definio pode tambm ser feita usando definies locais, tornando-a mais fcil de ser
compreendida, da seguinte forma.
1. A funo fazpares:
2. A funo pares:
Comentrios
No exemplo 1) deve ser observada a forma como as expresses so construdas. O primeiro
elemento escolhido, a, vem da lista l. Para ele, so construdos todos os pares possveis com
os elementos, b, que vm do outro gerador, a lista m. Assim toma-se o elemento 1 da lista
[1,2,3] e formam-se os pares com os elementos 4 e 5 da lista [4,5]. Agora escolhe-se o segundo
elemento da lista l, 2, e formam-se os pares com os elementos da lista m. Finalmente, repete-se
este processo para o terceiro elemento da lista l. Esta forma de construo tem importncia
fundamental, sendo responsvel pela construo de listas potencialmente infinitas, um tpico
descrito no prximo Captulo. Neste exemplo, tambm se nota que uma expresso ZF pode no
ter qualquer expresso boolena.
93
No exemplo 2) deve-se notar a importncia que tem ordem em que os geradores so colocados.
Se o gerador de b viesse antes do gerador de a, ocorreria um erro.
No exemplo 3) tambm deve ser observada a ordem em que os geradores foram colocados
para que seja possvel a gerao correta dos tringulos retngulos.
A funo livrosEmprestados, definida na seo 4.2, pode ser re-definida usando expresses
ZF, da seguinte forma:
Exerccios:
2. Como a funo membro :: [Int] > Int > Bool pode ser definida usando compreenso
de listas e um teste de igualdade?
Exemplo. Vamos agora mostrar um exemplo mais completo, baseado na referncia [46], que
mostra algumas das possibilidades que as compreenses oferecem. Seja um processador de texto
simples que organiza um texto, identando-o pela esquerda. Por exemplo, o texto
espacoEmBranco :: [Char]
espacoEmBranco = [\n, \t, ]
Vamos definir a funo pegaPalavra que, quando aplicada a uma string, retira a primeira
palavra desta string se a string no iniciar com um espao em branco. Assim pegaPalavra
"bicho besta"= "bicho" e pegaPalavra " bicho"= [ ] porque a string iniciada com um
caractere em branco.
Uma definio para ela pode ser feita, usando a funo pertence, definida na seo 4.1, que
verifica se um determinado caractere a pertence, ou no, a uma string:
94
J a funo tiraPalavra, quando aplicada a uma string, retira a primeira palavra da string
e retorna a string restante, tendo como seu primeiro caractere o espao em branco. Assim,
tiraPalavra "bicho feio"= " feio".
Est claro que necessrio construir uma funo para retirar os espaos em branco da frente
das palavras. Esta funo ser tiraEspacos que, aplicada a uma string iniciada com um ou
mais espaos em branco, retorna outra string sem estes espaos em branco.
Resta agora formalizar como uma string, st, deve ser transformada em uma lista de palavras,
assumindo que st no seja iniciada por um espao em branco. Assim,
a primeira palavra desta lista de palavras ser dada por pegaPalavra st,
o restante da lista ser construdo dividindo-se a string que resulta da remoo da primeira
palavra e dos espaos em branco que a seguem, ou seja, a nova transformao ser feita
sobre tiraEspacos (tiraPalavra st).
95
Deve ser observado que j existe a funo words, definida no Prelude, que produz o mesmo
resultado que a funo divideEmPalavras. Esta definio foi aqui mostrada para fins de
entendimento do usurio. Agora necessrio tomar uma lista de palavras e transform-la em
uma lista de linhas, onde cada linha uma lista de palavras, com um tamanho mximo (a linha).
Para isto, vamos definir uma funo que forme uma nica linha com um tamanho determinado.
Se a primeira palavra disponvel for p, ela far parte da linha se existir vaga para ela na
linha. O tamanho de p, length p, ter que ser menor ou igual ao tamanho da linha (tam).
O restante da linha construdo a partir das palavras que restam, considerando agora uma
linha de tamanho tam-(length p + 1).
formaLinha tam [ ] = [ ]
formaLinha tam (p:ps)
|length p <= tam = p : restoDaLinha
|otherwise = [ ]
where
novoTam = tam - (length p + 1)
restoDaLinha = formaLinha novoTam ps
Precisamos criar uma funo, tiraLinha, que receba como parmetros um tamanho que uma
linha deve ter e uma lista de palavras e retorne esta lista de palavras sem a primeira linha. Esta
funo ser deixada como exerccio, no entanto, indicamos seu tipo.
Agora necessrio juntar as coisas. Vamos construir uma funo que transforme uma lista de
palavras em uma lista de linhas. Primeiro ela forma a primeira linha, depois retira as palavras
desta linha da lista original e aplica a funo recursivamente lista restante.
96
Falta agora construir uma funo que transforme uma string em uma lista de linhas, formando
o novo texto identado esquerda.
Finalmente deve-se juntar as linhas para que se tenha o novo texto, agora formando uma
string identada esquerda. Ser mostrada apenas o seu tipo, deixando sua definio como
exerccio.
Exerccios
1. D uma definio de uma funo juntaLinha :: Linha > String que transforma uma
linha em uma forma imprimvel. Por exemplo, juntaLinha ["bicho", "bom"] = "bicho
bom".
2. Use a funo juntaLinha do exerccio anterior para definir uma funo juntaLinhas ::
[Linha] > String que junta linhas separadas por \n.
3. Modifique a funo juntaLinha de forma que ela ajuste a linha ao tamanho tam, adicio-
nando uma quantidade de espaos entre as palavras.
4. Defina uma funo estat :: String > (Int, Int, Int) que aplicada a um texto retorna
o nmero de caracteres, palavras e linhas do texto. O final de uma linha sinalizado pelo
caractere newline (\n). Defina tambm uma funo novoestat :: String > (Int, Int,
Int) que faz a mesma estatstica sobre o texto, aps ser ajustado.
5. Defina uma funo subst :: String > String > String > String de forma que
subst velhaSub novaSub st faa a substituio da sub-string velhaSub pela sub-string
novaSub em st. Por exemplo, subst "much" "tall" "How much is that?"= "How
tall is that?" (Se a sub-string velhaSub no ocorrer em st, o resultado deve ser st).
Provavelmente, a maioria dos leitores j estejam familiarizados com a idia de listas de listas,
de dar nomes s listas ou de funes que adimitem listas como seus parmetros. O que talvez
parea extranho para muitos a idia de listas de funes ou de funes que retornam outras
funes com resultados. Esta uma caracterstica importante das linguagens funcionais, A idia
central a de que as funes so consideradas com os mesmos direitos que qualquer outro tipo
de dado, dizendo-se, corriqueiramente, que elas so cidads de primeira categoria.
Vamos imaginar uma funo twice que, quando aplicada a uma outra funo, por exemplo,
f, produza, como resultado, uma outra funo que aplicada a seu argumento tenha o mesmo
efeito da aplicao da funo f, duas vezes. Assim,
twice f x = f (f x)
97
O resultado desta aplicao 4. O efeito de soma x criar uma funo chamada somax,
que adiciona x a algum nmero natural, no caso, y. Desta forma, suc uma funo que adiciona
o nmero 1 a um nmero natural qualquer.
A partir destes exemplos, podemos caracterizar duas ferramentas importantes, a saber:
1. Um novo mecanismo para passar dois ou mais parmetros para uma funo. Apesar da
funo soma ter sido declarada com apenas um parmetro, poderamos cham-la, por
exemplo, como soma 3 4, que daria como resultado 7. Isto significa que soma 3 tem
como resultado uma outra funo que, aplicada a 4, d como resultado o valor 7.
2. Um mecanismo de aplicao parcial de funes. Isto quer dizer que, se uma funo for
declarada com n parmetros, podemos aplic-la a m destes parmetros, mesmo que m seja
menor que n.
Um padro de computao que explorado como funo de alta ordem envolve a criao de uma
lista (listaNova) a partir de uma outra lista (listaVelha), onde cada elemento de listaNova
tem o seu valor determinado atravs da aplicao de uma funo a cada elemento de listaVelha.
Suponhamos que se deseja transformar uma lista de nomes em uma nova lista de tuplas, em
que cada nome da primeira lista transformado em uma tupla, onde o primeiro elemento seja o
prprio nome e o segundo seja a quantidade de caracteres do nome. Para isso, ser construda
uma funo listaTupla de forma que
Antes vamos criar a funo auxiliar tuplaNum que, aplicada a um nome, retorna a tupla
formada pelo nome e a quantidade de caracteres do nome.
Vamos agora, supor que se deseja transformar uma lista de inteiros em uma outra lista de
inteiros, onde cada valor inteiro da primeira lista seja transformado em seu dobro, ou seja,
Como na definio da funo anterior, vamos construir a funo auxiliar, dobra, da seguinte
forma:
98
dobra :: Int -> Int
dobra n = 2*n
mais fcil entender a definio, porque torna claro que se trata de um mapeamento, por
causa da funo map. Precisa apenas entender a funo mapeada.
mais fcil modificar as definies das funes a serem aplicadas, se isto for necessrio.
99
mais fcil reutilizar as definies.
Muitas outras funes podem ser definidas usando map. Por exemplo,
3. Estabelea o tipo e defina uma funo trwice que toma uma funo de inteiros para inteiros
e um inteiro e retorna a funo aplicada entrada trs vezes. Por exemplo, com a funo
triplica e o inteiro 4 como entradas, o resultado 108.
4. D o tipo e defina uma funo iter de forma que iter n f x = f ( f ( f . . . (f x) . . .)),
onde f ocorre n vezes no lado direito da equao.
Por exemplo, devemos ter: iter 3 f x = f ( f ( f x )) e iter 0 f x = x.
5. Usando iter e duplica, defina uma funo que aplicada a n retorne 2n .
J foi visto, e de forma enftica, que, nas linguagens funcionais, as funes podem ser usadas
como parmetros para outras funes. No entanto, seria um desperdcio definir uma funo que
s pudesse ser utilizada como parmetro para outra funo, ou seja, a funo s fosse utilizada
neste caso e em nenhum outro mais. No caso da seo anterior, as funes dobra e tuplaNum,
possivelmente, s sejam utilizadas como argumentos das funes dobraLista e listaTupla.
Uma forma de declarar funes para serem utilizadas apenas localmente usar a clusula
where. Por exemplo, dado um inteiro n, vamos definir uma funo que retorne uma outra funo
de inteiro para inteiro que adiciona n a seu argumento.
100
somaNum :: Int -> (Int -> Int)
somaNum n = h where h m = n + m
\m -> n + m
Vamos agora analisar a sintaxe de uma funo annima. Uma definio annima dividida
em duas partes: uma antes da flexa e a outra depois dela. Estas duas partes tm as seguintes
interpretaes:
A barra invertida no incio (\) indica que se trata de uma funo annima. A \ o caractere
mais parecido com a letra grega , usada no -clculo.
Vejamos a funo comp2, mostrada graficamente na Figura 4.1. Na realidade, trata-se de
uma funo g que recebe como entrada dois argumentos, no caso f x e f y, que so os resultados
das aplicaes da funo f aos argumentos x e y, ou seja g (f x) (f y).
x fx
f
g g (f x) (f y)
y fy
f
A definio de comp2
101
comp2 quad soma 5 6
onde quad e soma tm significados bvios. De forma geral, sendo f definida por
f x y z = resultado
\x y z -> resultado
Uma outra funo de alta ordem, tambm de grande utilizao em aplicaes funcionais, a
funo fold, usada na combinao de tens (folding). Ela toma como argumentos uma funo de
dois argumentos e a aplica aos elementos de uma lista. O resultado um elemento do tipo dos
elementos da lista. Vamos ver sua definio formal e exemplos de sua aplicao.
Exemplos:
fold (||) [False, True, False] = (||) False (fold (||) [True, False])
= (||) False ((||) True (fold (||) [False])
= (||) False ((||) True False)
= (||) False True
= True
fold (++) ["Chico", "Afonso", , "!"] = "Chico Afonso!"
fold (*) [1..6] = 720 (Verifique!)
No entanto, existe um pequeno problema na definio de fold. Se ela for aplicada a uma
funo e uma lista vazia ocorrer um erro. Para resolver este impasse, foi pr-definida, em
Haskell, uma outra funo para substituir fold, onde este tipo de erro seja resolvido. Esta a
funo foldr, definida da seguinte forma:
102
A funo filter
A funo filter uma outra funo de alta ordem, pr-definida em todas as linguagens funcionais,
de bastante utilizao. Resumidamente, ela escolhe dentre os elementos de uma lista, aqueles
que tm uma determinada propriedade. Vejamos alguns exemplos:
3. A lista de todos os nmeros pares maiores que 113 e menores ou iguais a 1000, que sejam
perfeitos: filter perfeito [y | y < [114 .. 1000], ehPar y].
4.6 Polimorfismo
Uma caracterstica muito importante das linguagens funcionais que suas definies podem ser
polimrficas, um mecanismo que aumenta o poder de expressividade de qualquer linguagem.
Polimorfismo uma das caractersticas responsveis pela alta produtividade de software, pro-
porcionada pelo aumento da reusabilidade.
Polimorfismo a capacidade de aplicar uma mesma funo a vrios tipos de dados, rep-
resentados por um tipo varivel. No deve ser confundido com sobrecarga, denominada por
muitos pesquisadores como polimorfismo ad hoc, que consiste na aplicao de vrias funes com
o mesmo nome a vrios tipos de dados. Haskell permite os dois tipos de polimorfismo, sendo que
a sobrecarga feita atravs de um mecanismo engenhoso chamado de type class, um tema a
ser estudado no prximo Captulo.
A funo length pr-definida em Haskell, da seguinte maneira:
103
length :: [t] -> Int
length [ ] = 0
length (a : x) = 1 + length x
Esta funo tem um tipo polimrfico porque pode ser aplicada a qualquer tipo de lista
homognea. Em sua definio no existe qualquer operao que exija que a lista parmetro seja
de algum tipo particular. A nica operao que esta funo faz contar os elementos de uma
lista, seja ela de que tipo for.
J a funo
no pode ser polimrfica porque s aplicvel a elementos onde a operao de multiplicao (*)
seja possvel. Por exemplo, no pode ser aplicada a strings, nem a valores booleanos.
Quando uma funo tem um tipo envolvendo um ou mais tipos variveis, diz-se que ela tem
um tipo polimrfico. Por exemplo, j vimos anteriormente que a lista vazia um elemento de
qualquer tipo de lista, ou seja, [ ] est na interseo dos tipos [Int], [Bool] ou [Char], etc. Para
explicitar esta caracterstica denota-se que [ ] :: [t], sendo t uma varivel que pode assumir
qualquer tipo. Assim, cons tem o tipo polimrfico (:) :: t > [t] > [t].
As seguintes funes, algumas j definidas anteriormente, tm os tipos:
Alguma dificuldade pode surgir nas definies dos tipos das funes quando elas envolvem tipos
variveis, uma vez que podem existir muitas instncias de um tipo varivel. Para resolver este
dilema, necessrio que o tipo da funo seja o tipo mais geral possvel, que definido da seguinte
forma:
Definio. Um tipo w de uma funo f o tipo mais geral de f se todos os tipos de f forem
instncias de w.
A partir desta definio pode-se observar que o tipo [t] > [t] > [(t, t)] uma instncia
do tipo da funo zip, mas no o tipo mais geral, porque [Int] > [Bool] > [(Int, Bool)]
um tipo para zip, mas no uma instncia de [t] >[t] >[(t,t)].
Exemplos de algumas funes polimrficas.
104
fst (x, _) = x snd (_, y) = y
em linguagens no polimrficas, as funes devem ser re-definidas para cada novo tipo.
Isto implica em ineficincia e insegurana.
Exerccios
1. Defina uma funo concat onde concat [e1 , ..., ek ] = e1 ++ ... ++ ek . Qual o tipo
de concat?
2. Defina uma funo unZip que transforma uma lista de pares em um par de listas. Qual o
seu tipo?
3. Defina uma funo last :: [t] > t que retorna o ltimo elemento de uma lista no
vazia. Defina tambm init :: [t] > [t] que retorna todos os elementos de uma lista com
exceo de seu ltimo elemento.
4. Defina funes tome, tire :: Int > [t] > [t] onde tome n l retorna os n primeiros
elementos da lista l e tire n l retira os n primeiros elementos da lista l.
Esquema de prova
O esquema de provas mostrado a seguir devido a Simon Thompson [46]. Apesar de muito
formal, ele deve ser seguido, principalmente, por iniciantes, que ainda no tm experincia com
provas de programas. Para estes usurios, recomendvel utiliz-lo como forma de treinamento.
Muitas pessoas tendem a querer chegar concluso de uma prova forando situaes, sem a
argumentao adequada e este tipo de vcio h que ser evitado, a qualquer custo. A falta de
domnio nesta rea pode levar o usurio a concluses equivocadas.
Outro erro, comumente cometido por algumas pessoas no afeitas a provas matemticas,
consiste em realizar provas sem se importar com as concluses das mesmas. A concluso parte
105
integrante do esquema de provas e, portanto, indispensvel. Ela o objetivo da prova. Sem
ela no existe razo para todo um esforo a ser despendido nas fases anteriores. A concluso
representa o desfecho de uma prova e representa a formalizao de uma proposio que passa a
ser verdadeira e pode ser utilizada em qualquer etapa de uma computao.
O esquema de provas deve se constituir nos seguintes estgios:
Estgio 0: escrever o objetivo da prova informalmente,
Estgio 1: escrever o objetivo da prova formalmente,
Estgio 2: escrever os sub-objetivos da prova por induo:
P([ ]) e
P(a : x), assumindo P(x)
Estgio 3: provar P([ ])
Estgio 4: provar P(a : x), lembrando que PODE e DEVE usar P(x).
Vejamos agora, dois exemplos completos de esquemas de provas que envolvem todos os est-
gios enumerados anteriormente.
Exemplo 1. Dadas as definies a seguir:
somaLista [ ] = 0 (1)
somaLista (a : x) = a + somaLista x (2)
dobra [ ] = [ ] (3)
dobra (a : x) = (2 * a) : dobra x (4)
Estgio 0: o dobro da soma dos elementos de uma lista igual soma dos elementos da
lista formada pelos dobros dos elementos da lista anterior.
Estgio 1: 2 * somaLista x = somaLista (dobra x) (5)
Estgio 2:
2 * somaLista [ ] = sumList (dobra [ ]) (6)
2 * somaLista (dobra (a : x)) = somaLista (dobra (a : x)) (7)
assumindo que 2 * somaLista x = somaLista (dobra x) (8)
Estgio 3: Caso base:
lado esquerdo do caso base: lado direito do caso base
2 * somaLista [ ] somaLista (dobra [ ]
=2*0 por (1) = somaLista [ ] por (3)
=0 pela aritmtica =0 por (1)
Assim, a assertiva (6) vlida.
Estgio 4: Passo indutivo:
lado esquerdo do passo indutivo:
2 * somaLista (a : x)
= 2 * (a + somaLista x) por (2).
lado direito do passo indutivo:
somaLista (dobra (a : x))
= somaLista (2 * a : dobra x) por (4)
= 2 * a + somaLista (dobra x) por (2)
= 2 * a + 2 * somaLista x pela hiptese de induo
= 2 * (a + somaLista x) pela distributividade de *.
106
Assim, a assertiva (7) vlida.
Concluso: como a assertiva (5) vlida para o caso base e para o passo indutivo, ento ela
vlida para todas as listas finitas.
Exemplo 2. Associatividade de append: x ++ (y ++ z) = (x ++ y) ++ z.
[ ] ++ v = v (1)
(a : x) ++ v = a : (x ++ v) (2)
x ++ (y ++ z) = (x ++ y) ++ z
Estgio 2:
Estgio 3:
caso base: lado esquerdo caso base: lado direito
[ ] ++ (y ++ z) ([ ] ++ y) ++ z
= y ++ z por (1) = y ++ z por (1)
Como o lado esquerdo e o lado direito do caso base so iguais, ento a propriedade vlida
para ele.
Estgio 4:
passo indutivo: lado esquerdo
(a : x) ++ (y ++ z)
= a : (x ++ (y ++ z)) por (2)
Concluso: como a assertiva vlida para o caso base e para o passo indutivo, ento ela
verdadeira para todas as listas finitas.
Exerccios
107
3. Prove que, para todas as listas finitas x e y, somaLista (x ++ y) = somaLista x +
somaLista y e que somaLista (x ++ y) = somaLista (y ++ x).
Uma forma simples de estruturar um programa constru-lo em etapas, uma aps outra, onde
cada uma delas pode ser definida separadamente. Em programao funcional, isto feito atravs
da composio de funes, uma propriedade matemtica s implementada nestas linguagens e
que aumenta, enormemente, a expressividade do programador.
A expresso f(g(x)), normalmente, escrita pelos matemticos como (f.g)(x), onde o .
(ponto) o operador de composio de funes. Esta notao importante porque separa a
parte das funes da parte dos argumentos. O operador de composio um tipo de funo,
cujos argumentos so duas funes e o resultado tambm uma funo.
Para facilitar nosso entendimento, vamos retornar funo divideEmPalavras, definida na
seo 4.3 da seguinte forma:
A resposta a esta aplicao 16. O interesse maior aqui est na definio da funo compoe.
Ela toma um par de parmetros, f e g (ambos funes) e retorna uma outra funo, h, cujo
efeito de sua aplicao a composio das funes f e g. Esta forma de codificao torna a
composio explcita sem a necessidade de aplicar cada lado da igualdade a um argumento.
Como sabido, nem todo par de funes pode ser composto. O tipo da sada da funo g
tem de ser o mesmo tipo da entrada da funo f. O tipo de . : ( . ) :: (u > v) > (t
> u) > (t > v).
A composio associativa, ou seja, f . (g . h) = (f . g) . h, que deve ser interpretado
como faa h, depois faa g e finalmente faa f ".
108
4.8.1 Composio avanada
A ordem em f . g importante (faa g e depois f ). Podemos fazer a composio ter o sentido das
aes propostas, usando a composio avanada. Deve ser lembrado que esta forma apenas para
fins de apresentao, uma vez que a definio, como ser vista, feita em funo da composio e
nada aumenta em expressividade. A composio avanada ser denotada por > . > indicando a
sequencializao da aplicao das funes, uma vez que, na composio, ela realizada na ordem
inversa de aparncia no texto. A definio de > . > feita da seguinte forma:
infixl 9 >.>
(>.>) :: (t -> u) -> (u -> v) -> (t -> v)
g >.> f = f . g
(twice succ) 12
= (succ . succ) 12 --pela definicao de twice
= succ (succ 12) --pela definicao de .
= succ 13 = 14
Pode-se generalizar twice, indicando um parmetro que informe quantas vezes a funo deve
ser composta com ela prpria:
Por exemplo, podemos definir 2n como iter n duplica e vamos mostrar a seqncia de
operaes para n=2.
Nesta definio, usamos o fato de que f . id = f. Estamos tratando de uma nova espcie de
igualdade, que a igualdade de duas funes. Mas como isto pode ser feito? Para isto, devemos
109
examinar como os dois lados se comportam quando os aplicamos a um mesmo argumento x.
Ento,
(f . id) x
= f (id x) pela definio de composio
=fx pela definio de id.
Isto significa que para um argumento x, qualquer, as duas funes se comportam exatamente
da mesma forma.
Vamos fazer uma pequena discusso sobre o que significa a igualdade entre duas funes.
Existem dois princpios que devem ser observados quando nos referimos igualdade de funes.
So eles:
Se estivermos interessados nos resultados de nossos programas, tudo o que nos interessa so
os valores dados pelas funes e no como estes valores so encontrados. Em Haskell, devemos
usar extensionalidade quando estivermos interessados no comportamento das funes. Se estiver-
mos interessados na eficincia ou outros aspectos de desempenho de programas devemos usar a
intencionalidade.
Exerccios
Exemplo: Seja a definio de map e da composio de funes dadas a seguir. Mostre que
map (f. g) x = (map f . map g) x
map f [ ] = [ ] (1)
map f (a : x) = f a : map f x (2)
(f . g) x = f (g x) (3)
Prova:
Caso base: a lista vazia, [ ]:
Lado esquerdo Lado direito
map (f . g) [ ] = [ ] por (1) (map f . map g) [ ]
= map f (map g [ ]) por (3)
=map f [ ] por (1)
=[ ] por (1)
110
Passo indutivo: (a : x)
Lado esquerdo Lado direito
map (f . g) (a : x) (map f . map g)(a:x)
=(f . g) a : map (f . g) x (2) = map f (map g (a:x)) (3)
=f (g a) : map (f . g) x (3) =map f ((g a) : (map g x)) (2)
=f (g a) : (map f . map g) x (hi) =f (g a) : (map f ( map g) x) (3)
=f (g a) : (map f . map g) x
Concluso. Como a propriedade vlida para a lista vazia e para a lista no vazia, ento ela
vlida para qualquer lista homognea finita.
Exerccio: Prove que para todas as listas finitas l e funes f, concat (map (map f ) l) =
map f (concat l).
Uma caracterstica importante de Haskell e que proporciona uma forma elegante e poderosa de
construo de funes a avaliao parcial que consiste na aplicao de uma funo a menos
argumentos que ela realmente precisa. Por exemplo, seja a funo multiplica que retorna o
produto de seus argumentos:
Esta funo foi declarada para ser usada com dois argumentos. No entanto, ela pode ser
chamada como multiplica 2. Esta aplicao retorna uma outra funo que, aplicada a um
argumento b, retorna o valor 2*b. Esta caracterstica o resultado do seguinte princpio em
Haskell: uma funo com n argumentos pode ser aplicada a r argumentos, onde r n. Como
exemplo, a funo dobraLista pode ser definida da seguinte forma:
onde multiplica 2 uma funo de inteiro para inteiro, a aplicao de multiplica a um de seus
argumentos, 2, em vez de ser aplicada aos dois. map (multiplica 2) uma funo do tipo [Int]
> [Int], dada pela aplicao parcial de map.
Como determinado o tipo de uma aplicao parcial? Pela regra do cancelamento: se uma
funo f tem o tipo t1 > t2 > ... > tn > t e aplicada aos argumentos e1 :: t1 , e2 ::
t2 , ...ek :: tk , com k n, ento o tipo do resultado dado pelo cancelamento dos tipos t1 at
tk , dando o tipo tk+1 > tk+2 > ...tn > t.
Por exemplo,
Mas afinal, quantos argumentos tem realmente uma funo em Haskell? Pelo exposto, a
resposta correta a esta questo 1 (UM). Isto currificao. Lembra-se do -clculo?
Isto significa que uma funo do tipo Int > Int > Int do mesmo tipo que Int
> (Int > Int). Neste ltimo caso, est explcito que esta funo pode ser aplicada a um
111
argumento inteiro e o seu resultado uma outra funo que recebe um inteiro e retorna outro
inteiro. Exemplificando,
multiplica :: Int -> Int -> Int ou multiplica :: Int -> (Int -> Int)
multiplica 4 :: Int -> Int
multiply 4 5 :: Int
Associatividade:
A aplicao de funo associativa esquerda, ou seja:
f a b = (f a) b
enquanto a flexa associativa pela direita:
t > u > v = t > (u > v).
Uma decorrncia direta das aplicaes parciais que representa uma ferramenta poderosa e ele-
gante em algumas linguagens funcionais e, em particular, em Haskell, so as sees de operadores.
As sees so operaes parciais, normalmente relacionadas com as operaes aritmticas. Nas
aplicaes, elas so colocadas entre parnteses. Por exemplo,
Uma seo de um operador op coloca o argumento no lado que completa a aplicao. Por
exemplo, (op a) b = b op a e (a op) b = a op b.
Exemplos:
Exemplo.
Este mais um exemplo que mostra o poder de expressividade de Haskell. Trata-se da criao
de um ndice remissivo, encontrado na maioria dos livros tcnicos. Este exemplo baseado no
112
livro de Simon Thompson [46]. Algumas funes j foram definidas anteriormente, no entanto
elas sero novamente definidas aqui para evitar ambigidades. Vamos mostrar, inicialmente, os
tipos de dados utilizados na simulao.
Neste caso, o texto uma string como a seguinte, onde no sero colocados os acentos e os
tis para facilitar o entendimento:
doc :: Doc
doc = "Eu nao sou cachorro nao\nPra viver tao humilhado\nEu nao sou
cachorro nao\nPara ser tao desprezado"
Vamos utilizar este trecho e mostrar como ele vai ser transformado com a aplicao das
funes que sero definidas para realizar operaes sobre ele:
113
Agora devemos juntar as linhas que contm uma mesma palavra em uma mesma lista de
linhas
[([1,3], "cachorro"), ([4], "desprezado"), ([1,3], "Eu"), ([2], "humilhado"), ([1,3], "nao"),
([4], "Para"), ([2], "Pra"), ([4], "ser"), ([1,3], "sou"), (2,4], "tao"), ([2], "viver")]
Esta operao realizada por mistura :: [([Int], Palavra)] > [([Int], Palavra)]
Vamos agora diminuir a lista, removendo todas as entradas para palavras com menos de 3
letras
[([1,3], "cachorro"), ([4], "desprezado"), ([2], "humilhado"), ([1,3], "nao"),
([4], "Para"), ([2], "Pra"), ([4], "ser"), ([1,3], "sou"), ([2,4], "tao"), ([2], "viver")]
Esta operao realizada por diminui :: [([Int], Palavra)] > [([Int], Palavra)]
importante notar que j existe no Prelude a funo lines, cujo resultado o mesmo da
funo formaLinhas. A definio de formaLinhas foi colocada aqui por motivos puramente
114
didtico-pedaggicos. O leitor deve se sentir incentivado a verificar este fato. Veremos, em
seguida, a definio da funo numDeLinhas.
Agora vamos definir a funo de ordenao usando a tcnica aplicada no algoritmo de or-
denao por quicksort. Para que isto seja possvel, necessrio fazer a comparao entre dois
pares, onde o primeiro elemento do par um nmero inteiro e o segundo uma palavra. Temos
que comparar dois pares deste tipo para saber dentre eles qual deve vir primeiro e qual deve vir
depois. Esta funo ser chamada de menor, definida a seguir:
A funo ordenaLista deve ser definida de forma que pares repetidos no apaream dupli-
cados. No exemplo, o par (1, nao) e (3, nao) so duplicados, mas eles s aparecem uma vez. O
leitor deve observar como esta exigncia foi implementada na funo.
115
ordenaLista (a : x) = ordenaLista menores ++ [a] ++ ordenaLista maiores
where menores = [b | b <- x, menor b a]
maiores = [b | b <- x, menor a b]
Com a lista ordenada, vamos transformar cada par da forma (Int, String) em um par da
forma ([Int], String), ou seja, vamos transformar cada inteiro em uma lista unitria de inteiros.
Esta operao definida da seguinte forma:
Neste ponto, vamos concatenar as listas de inteiros que contm a mesma palavra, formando
uma lista nica composta de todas as linhas que contm a mesma palavra. Esta operao
realizada pela funo mistura, definida da seguinte forma:
Finalmente, sero retiradas da lista as palavras com menos de 3 letras. Esta operao foi colo-
cada apenas para efeito de mostrar como pode ser definda, mas, na realidade, ela dispensvel.
Esta operao definida da seguinte forma:
Este exemplo visa mostrar que as linguagens funcionais no foram projetadas apenas para
fazer clculos matemticos, como fibonacci e fatorial. Na realidade, Haskell serve tambm
para estes clculos, mas sua rea de aplicao muito mais ampla do que imaginada por
alguns programadores, defensores intransigentes de outros paradigmas de programao. O leitor
deve reler o Captulo introdutrio deste estudo e consultar as referncias [50, 13] para maiores
informaes sobre este tema ou consultar o site oficial de Haskell.
Cifragem por transposio. Este exemplo, mostra uma aplicao importante na rea da
Criptografia. Trata-se de codificar uma determinada mensagem que deve ser enviada a algum
e que deve ser protegida contra a leitura, caso a mensagem seja interceptada por algum, seja
ele o prprio mensageiro ou uma outra pessoa. O receptor da mensagem deve conhecer uma
chave, sem caracteres repetidos, com a qual deve decriptografar a mensagem. Para este exemplo,
vamos supor que Tom Jobim deseja enviar a mensagem EU SEI QUE VOU TE AMAR POR
TODA A MINHA VIDA EU VOU TE AMAR EM CADA DESPEDIDA EU VOU TE AMAR
para uma sua namorada e que ambos haviam combinado como chave a palavra COMPUTER.
Para entender melhor, colocamos nmeros sobre cada letra da chave, indicando a sua ordem de
ocorrncia alfabtica dentro da chave. Por exemplo, a primeira letra que ocorre no alfabeto,
dentre as da palavra a letra C, a segunda a letra E e assim por diante. A mensagem ser
escrita na horizontal mas ser enviada por coluna, iniciando com a coluna 1, depois com a coluna
2 e assim por diante.
116
1 4 3 5 8 7 2 6
C O M P U T E R
E U S E I Q
U E V O U T
E A M A R P
O R T O D A
A M I N H A
V I D A E U
V O U T E A
M A R E M C
A D A D E S P
E D I D A E U
V O U T E
A M A R
Desta forma, a mensagem cifrada deve ser a seguinte, esclarecendo que cada espao em branco
tmbm considerado um caractere.
O primeiro processamento a ser feito com a String original divid-la em palavras do tamanho
de cada coluna. Para isso construmos a funo div_em_col da seguinte forma:
Esta funo utiliza as funes pega_col, que retorna uma palavra do tamanho de uma
coluna a partir de uma String, e a funo tira_col que retira uma palavra do tamanho de uma
coluna de uma String, retornando a nova String sem os caracteres referentes a esta palavra.
Os caracteres que formam a chave devem ser colocados em ordem crescente para se saber a
ordem em que cada coluna foi colocada na mensagem cifrada. Para isso ser utilizado o algoritmo
quicksort j implementado anteriormente neste Captulo. Utiliza-se a funo zip para construir
pares de cada letra da chave ordenada com os nmeros naturais que vo representar a posio
da coluna, a partir de 0 (zero).
117
lista_pos :: String -> [Int]
lista_pos chave =
pega_lista_pos chave (zip (quicksort chave) [0..(length chave) -1])
necessrio que a tabela original seja reconstruda. Para isso necessrio saber a posio
em que cada coluna deve ser recolocada para reconstruir a tabela original. Isto feito atravs
da funo pega_lista_pos.
A funo pega_pos responsvel por retornar a posio em que cada caractere tem na
chave.
A construo da lista contendo todas as colunas na ordem em que foram construdas feita
pela funo lista.
A funo lista_int utiliza a funo lista com o texto cifrado dividido em palavras com o
tamanho de cada coluna.
A funo pega constri cada linha da tabela, tomando os caracteres de mesma posio em
cada coluna, formando uma linha da tabela original.
A lista contendo as linhas da tabela original construda atravs da funo lista_fin que
utiliza o parmetro k que representa o tamanho da chave.
lista_fin :: Int -> Int -> String -> String -> String
lista_fin n k txt ch
|n < k = pega n prim ++ lista_fin (n + 1) k txt ch
|otherwise = [ ] where prim = lista_int txt ch
118
mens_original :: String -> String -> String
mens_original txt ch = lista_fin 0 (div (length txt) (length ch)) txt ch
Busca de menor caminho em rvores. Este tipo de problema acontece com frequncia em
diversas reas da computao. Vamos utilizar um caso particular conhecido como o problema dos
baldes. O problema consiste na existncia de trs baldes, sendo um com capacidade para 8 litros,
outro com capacidade para 5 litros e o ltimo com capacidade para 3 litros. O problema consiste
em se verificar se a partir de uma quantidade de lquido nos baldes encontrar uma sequncia de
operaes que podem ser feitas com os baldes de forma a se atingir uma determinada configurao
desejada. A nica operao possvel a transferncia do lquido de um balde para o outro at
este atingir sua capacidade mxima.
Cada estado dos baldes em um determinado momento, pode ser representado por uma tripla
do tipo (Int, Int, Int), onde cada valor representa quanto cada balde tem de lquido neste mo-
mento. A soluo deste problema consiste em encontrar uma sequncia destas tuplas, onde a
ltima configurao deve corresponder a configurao desejada. Uma soluo interessante utiliza
rvores multirias, onde cada sequncia d origem a vrias sub-rvores formadas pelos sucessores
possveis para o ltimo estado de cada sequncia. Para implementar esta soluo em Haskell,
vamos definir o tipo de dado adequado.
As operaes de despejo de cada balde em outro pode ser representada de forma individual
onde cada balde pode colocar lquido em um outro. Vamos simbolizar os baldes por A, B e
C. Assim, tansferir o lquido do balde A em B pode ser respresentada pela seguinte funo em
Haskell:
Para transferir o lquido do balde A para o balde C, ou para a transferncia dos demais
baldes, estas operaes so implementadas da seguite forma:
119
|y + z > 3 = [(x, y+z-3, 3)]
|otherwise = [(x, 0, z+y)]
Dessa forma, os estados possveis a partir de uma determinada configurao sero os seguintes:
A funo achou verifica se uma sequncia com a especificao desejada foi encontrada.
Se a configurao desejada no tiver sido atingida ainda neste nvel, ento devemos encontrar
procurar as configuraes para um prximo nvel na rvore. Isto pode ser feito pela funo
map_suc:
120
Como as sequncias so descartadas a medida que um estado possvel j esteje na sequncia,
possvel que a configurao final pode ser representada por uma lista vazia. Se o estado desejado
for encontrado, ento deve ser mostrada esta sequncia. Isto feito pela funo resultado:
Para um caso particular, a partir de uma configurao em que o primeiro jarro contm 8 litros
de vinho e os outros dois esto vazios, ou seja, do estado (8,0,0), e se deseja atingir o estado em
que dois baldes tenham 4 litros e o terceiro esteja vazio, ou seja, a configurao final (4,4,0), a
menor sequncia de operaes ser representada pela lista
(8, 0, 0), (3, 5, 0), (3, 2, 3), (6, 2, 0), (6, 0, 2), (1, 5, 2), (1, 4, 3), (4, 4, 0)
desenvolvida em 8 etapas. Esta a menor sequncia, uma vez que outra poderia ser
(8, 0, 0), (3, 5, 0), (0, 5, 3), (5, 0, 3), (5, 3, 0), (2, 3, 3), (2, 5, 1), (7, 0, 1), (7, 1, 0), (4, 1, 3), (4, 4, 0)
desenvolvida em 11 etapas.
Exerccios propostos.
1. Defina, em Haskell, uma funo f que, dadas uma lista i de inteiros e uma lista l qualquer,
retorne uma nova lista constituda pela lista l seguida de seus elementos que tm posio
indicada na lista i, conforme o exemplo a seguir:
f [2,1,4] [a, b, c, d] = [a, b, c, d, d, a, b].
2. Usando compreenso, defina uma funo em Haskell, que gere todas as tuplas ordenadas
de nmeros x, y, e z menores ou iguais a um dado nmero n, tal que x2 + y 2 = z 2 .
3. Defina, em Haskell, uma funo que calcule o Determinante de uma matriz quadrada de
ordem n.
5. Defina, em Haskell, uma funo f que, dada uma lista l construa duas outras listas l1 e l2,
de forma que l1 contenha os elementos de l de posio mpar e l2 contenha os elementos
de l de posio par, preservando a posio dos elementos, conforme os exemplos a seguir:
f [a, b, c, d] = [[a, c], [b, d]]
f [a, b, c, d, e] = [[a, c, e], [b, d]].
6. Um pequeno visor de cristal lquido (LCD) contm uma matriz 5x3 que pode mostrar um
nmero, como 9 e 5, por exemplo:
*** ***
* * *
*** ***
* *
* ***
121
O formato de cada nmero definido por uma lista de inteiros que indicam quantos *s se
repetem, seguidos de quantos brancos se repetem, at o final da matriz 5x3, comeando da
primeira linha at a ltima:
nove, cinco, um, dois, tres, quatro, seis, sete oito, zero :: [Int]
nove = [4,1,4,2,1,2,1]
cinco = [4,2,3,2,4]
um = [0,2,1,2,1,2,1,2,1,2,1]
dois = [3,2,5,2,3]
tres = [3,2,4,2,4]
quatro = [1,1,2,1,4,2,1,2,1]
seis = [4,2,4,1,4]
sete = [3,2,1,2,1,2,1,2,1]
oito = [4,1,5,1,4]
zero = [4,1,2,1,2,1,4]
indicando que o nmero nove composto por 4 *s (trs na primeira linha e um na segunda),
seguida de 1 espao, mais 4 *s, 2 espaos, 1 *, 2 espaos e 1 *. Construa funes para:
122
numeros :: [[Int]]
numeros
= [zero, um, dois, tres, quatro, cinco, seis, sete, oito, nove]
f. Faa uma funo que, dada uma string de *s e espaos, retorne a representao da
string como [Int] no formato usado no LCD, ou seja, a funo inversa de toString.
toCompact :: String -> [Int]
toCompact "**** *** * *" = [4,1,4,2,1,2,1].
7. Defina, em Haskell, uma funo que aplicada a uma lista l e a um inteiro n, retorne todas
as sublistas de l com comprimento maior ou igual a n.
8. Voltando ao problema da criptografia resolvido neste Captulo, tente um novo procedimento
para criar um texto cifrado a partir de um texto pleno. Uma outra sugesto se refere
chave a ser utilizada. No caso mostrado, a chave no pode conter caracteres repetidos.
Imagine uma soluo para tratar chaves em que os dgitos possam ser repetidos.
9. Um nmero inteiro positivo conhecido como nmero de Mersenne se puder ser escrito
como Mn = 2n 1, para um nmero n inteiro no negativo. Existem nmeros de Mersenne
que so primos e outros que no so primos. Por exemplo, M1 = 1 (no primo), M2 = 3
(primo), M3 = 7 (primo), M4 = 15 (no primo), etc. O maior nmero primo de Mersenne
registrado at 30 de setembro de 2009 M46 = 243112609 1, com quase 13 milhes de
algarismos em sua representao decimal, e foi descoberto pelo Great Internet Mersenne
Prime Search. Para se ter idia da magnitude, para represent-lo em base decimal seriam
requeridas 3461 pginas com 50 linhas por pgina e 75 dgitos por linha. Defina em Haskell
uma funo que verifique se um dado nmero inteiro no negativo, x, , ou no, um primo
de Mersenne.
4.10 Resumo
Este Captulo foi dedicado inteiramente ao estudo das listas em Haskell, completando o estudo
dos tipos primitivos adotados em Haskell, alm do tipo estruturado produto cartesiano, represen-
tado pelas tuplas. Ele se tornou necessrio, dada a importncia que as listas tm nas linguagens
funcionais. Foi dada nfase s funes pr-definidas e s Compreenses, tambm conhecidas
por expresses ZF, mostrando a facilidade de construir funes com esta ferramenta da lin-
guagem. Vimos tambm a elegncia, a facilidade e a adequao com que algumas funes, como
por exemplo, o quicksort, foram definidas. Finalmente foram vistas caractersticas importantes
na construo de funes como: polimorfismo, composio, avaliao parcial e currificao de
funes.
Apesar dos tipos de dados estudados at aqui j significarem um avano importante, Haskell
vai mais alm. A linguagem tambm permite a criao de tipos algbricos de dados e tambm
dos tipos abstratos de dados, temas a serem estudados nos prximos Captulos.
Grande parte deste estudo foi baseado nos livros de Simon Thompson [46] e de Richard Bird
[4]. Estas duas referncias representam o que de mais prtico existe relacionado com exerccios
usando listas. O livro de Paul Hudak [11] tambm uma fonte de consulta importante, dada a
sua aplicao multimdia.
Uma fonte importante de problemas que podem ser resolvidos utilizando as ferramentas aqui
mostradas o livro de Steven S. Skiena e Miguel A. Revilla [39], que apresenta um catlogo
dos mais variados tipos de problemas, desde um nvel inicial at problemas de solues mais
elaboradas. Outra fonte importante de problemas matemticos envolvendo grafos o livro de
Sriram Pemmaraju e Steven Skiena [31].
123
124
Captulo 5
5.1 Introduo
Este Captulo dedicado construo de um novo tipo de dado, diferente dos apresentados at
agora, conhecido como tipo algbrico de dados. Estes tipos de dados facilitam a simulao de
problemas de forma mais natural e mais prxima do mundo real. A presena destes tipos de
dados em Haskell possibilitam que ela seja uma linguagem utilizvel em vrias reas de aplicao.
Vamos iniciar este estudo com as classes de tipos, conhecidas mais comumente como type
class. As classes de tipos so utilizadas na implementao dos tipos algbricos e dos tipos
abstratos de dados, estes ltimos um tema a ser estudado em outro Captulo.
Os tipos algbricos, representam um aumento na expressividade e no poder de abstrao
de Haskell. Por este motivo, este estudo deve ser de domnio pleno para se aproveitar as pos-
sibilidades que a linguagem oferece, construindo bons programas. Como exemplo, as rvores
so estruturas importantes na modelagem de vrios problemas e podem ser implementadas em
Haskell de forma muito simples usando os tipos algbricos de dados. Esta constatao pode ser
verificada pelo usurio ao final deste Captulo.
J foram vistas funes que atuam sobre valores de mais de um tipo. Por exemplo, a funo
length, pr-definida em Haskell, pode ser empregada para determinar o tamanho de listas de
qualquer tipo. Assim, length uma funo polimrfica, ou seja, a mesma definio pode ser
aplicada a listas homogneas de qualquer tipo. Por outro lado, algumas funes podem ser
aplicadas a mais de um tipo de dados, mas tm de apresentar uma nova implementao para
cada tipo sobre o qual elas vo atuar. So os casos das funes +, - e *, por exemplo. O algoritmo
utilizado para somar dois valores inteiros deferente do algoritmo para somar dois valores reais,
uma vez que as representaes dos inteiros difernte da representao de valores reais. Estas
funes so sobrecarregadas.
Existe uma discusso antiga entre os pesquisadores sobre as vantagens e desvantagens de se
125
colocar sobrecarga em uma linguagem de programao. Alguns argumentam que a sobrecarga
no aumenta o poder de expressividade da linguagem porque, mesmo tendo o mesmo nome,
as funes sobrecarregadas atuam de forma diferenciada sobre cada um deles. Sendo assim,
elas poderiam ter nomes distintos para cada definio. Outros admitem a sobrecarga como uma
necessidade das linguagens, uma vez que ela permite reusabilidade e a legibilidade dos programas
[46]. Por exemplo, seria tedioso usar um smbolo para a soma de inteiros e outro para a soma de
nmeros reais. Esta mesma situao se verifica nos operadores de subtrao e multiplicao. A
verdade que todas as linguagens de programao admitem alguma forma de sobrecarga. Haskell
no uma exceo e admite que seus operadores aritmticos pr-definidos sejam sobrecarregados.
Os tipos sobre os quais uma funo sobrecarregada pode atuar formam uma coleo de tipos
chamada classe de tipos (type class) em Haskell. Quando um tipo pertence a uma classe,
diz-se que ele uma instncia da classe. As classes em Haskell permitem uma hierarquia entre
elas, juntamente com um mecanismo de herana, de forma similar ao mecenismo de herana
encontrado nas linguagens orientadas a objeto.
A funo elem, definida a seguir, quando aplicada a um elemento e a uma lista de valores do
tipo deste elemento, verifica se ele pertence ou no lista, retornando um valor booleano.
Analisando a definio desta funo aplicada lista no vazia, verificamos que feito um
teste para verificar se o elemento x igual a cabea da lista (x==a). Para este teste utilizada
a funo de igualdade (==). Isto implica que a funo elem s pode ser aplicada a tipos cujos
valores possam ser comparados pela funo ==. Os tipos que tm esta propriedade formam
uma classe em Haskell.
Em Haskell, existem dois operadores de teste de igualdade: == e /=. Para valores booleanos,
eles so definidos da seguinte forma:
126
A forma de declarar Eq como a classe dos tipos que tm os operadores == e /= a seguinte:
class Eq t where
(==), (/=) :: t -> t -> Bool
Esta declarao estabelece que a classe Eq formada por todos os tipos que contm as
funes ou mtodos, == e /=. Dito de outra forma, as funes == e /= so definidas nos
tipos que compem a classe Eq. Estas funes tm o seguinte tipo:
Agora o leitor pode entender algumas mensagens de erros na declarao de funes mostradas
pelo sistema Haskell quando detecta algum erro de tipo. Normalmente, o sistema se refere a
alguma classe de tipos, nestes casos.
Analisemos agora a funo todosIguais que verifica se trs valores inteiros so iguais, ou no.
Observe, nesta definio, que no feita qualquer restrio que a obrigue a ser definida
somente para valores inteiros. A nica operao realizada com os elementos m, n e p uma
comparao entre eles atravs da funo de igualdade ==. Dessa forma, a funo todosIguais
pode ser aplicada a trs valores de um tipo t qualquer, desde que seus valores possam ser
comparados pela funo ==. Isto d a funo todosIguais um tipo mais geral, da seguinte
forma:
significando que a funo todosIguais no pode ser aplicada apenas a valores inteiros, mas
tambm a valores de qualquer tipo que esteja na classe Eq, ou seja, para os quais seja definido
um teste de igualdade (==). A parte antes do sinal => chamada de contexto que, neste caso,
a classe Eq. A leitura deste novo tipo deve ser: se um tipo t est na classe Eq ento a funo
todosIguais tem o tipo t > t > t > Bool. Isto significa que ela pode ter os seguintes
tipos: Int > Int > Int > Bool, Char > Char > Char > Bool ou ainda (Int,
Bool) > (Int, Bool) > (Int, Bool) > Bool, entre outros, uma vez que todos estes
tipos tm uma funo == definida para seus valores.
Vejamos agora o que acontece se ao tentar aplicar a funo todosIguais a argumentos do
tipo funo, como por exemplo, da funo suc definida da seguinte forma:
127
5.2.3 Assinaturas e instncias
Foi visto que o teste de igualdade (==) sobrecarregado, o que permite que ele seja utilizado em
uma variedade de tipos para os quais esteja definido, ou seja, para instncias da classe Eq. Ser
mostrado agora como as classes e instncias so declaradas, por exemplo, a classe Visible que
transforma cada valor em um String e d a ele um tamanho. Esta classe necessria porque
o sistema de entrada/sada de Haskell s permite a impresso de Strings, ou seja, para que
qualquer valor em Haskell seja impresso, necessrio que ele seja primeiro transformado em um
String, caso ainda no o seja.
A definio inclui o nome da classe (Visible) e uma assinatura, que so as funes que
compem a classe juntamente com seus tipos. Um tipo t para pertencer classe Visible tem
de implementar as duas funes da assinatura, ou seja, coisas visveis so coisas que podem ser
transformadas em um String e que tenham um tamanho. Por exemplo, para se declarar que o
tipo Char seja uma instncia da classe Visible, deve-se fazer a declarao
que mostra como um caractere deve ser transformado em um String de tamanho 1 para que
ele seja visvel. De forma similar, para declararmos o tipo Bool como uma instncia da classe
Visible, temos de declarar
Para que o tipo Bool seja uma instncia da classe Eq devemos fazer:
Uma classe em Haskell pode herdar as propriedades de outras classes, como nas linguagens
orientadas a objetos. Como exemplo, vamos observar a classe Ord que possui as operaes >,
>=, <, <=, max, min e compare, alm de herdar a operao == da classe Eq. Sua definio
feita da seguinte forma:
128
O tipo Ordering ser definido mais adiante, quando nos referirmos aos tipos algbricos de
dados. Neste caso, a classe Ord herda as operaes de Eq (no caso, == e /=). H a necessidade
de se definir pelo menos a funo < (pode ser outra) para ser utilizada na definio das outras
funes da assinatura. Estas definies formam o conjunto de declaraes a seguir:
x <= y = (x < y || x == y)
x > y = y < x
x >= y = (y < x || x == y)
Vamos supor que se deseja ordenar uma lista e mostrar o resultado como um String. Podemos
declarar a funo vSort para estas operaes, da seguinte forma:
onde toString e iSort so funes j definidas anteriormente. Para ordenar a lista necessrio
que ela seja composta de elementos que pertenam a um tipo t e que possam ser ordenados, ou
seja, pertenam a um tipo que esteja na classe Ord. Para converter o resultado em um String,
necessrio que a lista [t] pertena classe Visible. Desta forma, vSort deve herdar das classes
Ord e Visible e, portanto, tem o tipo
O caso em que um tipo herda de mais de uma classe conhecido na literatura como herana
mltipla. Esta propriedade implementada em Haskell, apesar de se verificar algumas discordn-
cias entre os projetistas de linguagens em relao a este tema. A herana mltipla tambm pode
ocorrer em uma declarao de instncia como:
mostrando que se dois tipos a e b estiverem na classe Eq ento o par (a, b) tambm est. A
herana mltipla tambm pode ocorrer na definio de uma classe. Por exemplo,
significando que os elementos da classe OrdVis herdam as operaes das classes Ord e Visible.
Este o caso da declarao de uma classe que no contm assinatura, ou contm uma assi-
natura vazia. Para estar na classe OrdVis, um tipo deve semplesmente estar nas classes Ord e
Visible. Neste caso, a definio da funo vSort anterior poderia ser modificada para
Exerccios
1. Como voc colocaria Bool, o tipo par (a, b) e o tipo tripla (a, b, c) como instncias do
tipo Visible?
2. Defina uma funo para converter um valor inteiro em uma String e mostre como Int
pode ser uma instncia de Visible.
129
5.2.5 As classes pr-definidas em Haskell
Haskell contm algumas classes pr-definidas. Nesta sub-seo, vamos ver algumas delas com
alguma explicao sobre a sua utilizao e definio.
A classe Eq
Esta classe, j mencionada anteriormente, composta pelos tipos cujos valores podem ser
comparados quanto a sua igualdade ou a sua diferena. Para isso, foram construdas as funes
de teste de igualdade, ==, e de diferena, /=, cujas definies so as seguintes:
class Eq t where
(==), (/=) :: t -> t -> Bool
x /= y = not (x == y)
x == y = not (x /= y)
A classe Ord
A classe Ord contm tipos, cujos valores podem ser ordenados, ou seja, podem ser testados
pelas funes <, , > e . Deve ser claro que os valores destes tipos tambm devem poder
ser testados quanto as suas igualdades e diferenas. Estes testes j so providos pela classe Eq,
portanto a classe Ord deve herdar estes testes da classe Eq. Assim, a classe Ord definida da
seguinte forma:
onde o tipo Ordering tem um entre trs valores possveis: LT, EQ e GT, que so os resultados
possveis de uma comparao entre dois valores. A definio de compare :
compare x y
|x == y = EQ
|x < y = LT
|otherwise = GT
A vantagem de se usar a funo compare que muitas outras funes podem ser definidas
em funo dela, por exemplo,
x <= y = compare x y /= GT
x < y = compare x y == LT
x >= y = compare x y /= LT
x > y = compare x y == GT
max x y
|x >= y = x
|otherwise = y
min x y
|x <= y = x
|otherwise = y
130
A maioria dos tipos em Haskell pertencem s classes Eq e Ord. As excees so as funes e
os tipos abstratos de dados, um tema que ser visto mais adiante, no Captulo 7 desta Apostila.
A classe Enum
Esta a classe dos tipos que podem ser enumerados, por exemplo, a lista [1,2,3,4,5,6] pode
tambm ser descrita por [1 .. 6] ou usando as funes da classe Enum, cuja definio a seguinte:
As funes fromEnum e toEnum tm as funes ord e chr do tipo Char como corres-
pondentes, ou seja ord e chr so definidas usando fromEnum e toEnum. Simon Thompson
[46] afirma que o Haskell report estabelece que as funes toEnum e fromEnum no so
significativas para todas as instncias da classe Enum. Para ele, o uso destas funes sobre
valores de ponto flutuante ou inteiros de preciso completa (Double ou Integer) resulta em
erro de execuo.
A classe Bounded
Esta uma classe que apresenta um valor mnimo e um valor mximo para a classe. Suas
instncias so Int, Char, Bool e Ordering. Sua definio a seguinte:
A classe Read
Esta classe contm os tipos cujos valores podem ser lidos a partir de Strings. Para usar a
classe necessrio apenas conhecer a funo read :: Read t => String > t. Esta classe
complementa a classe Show, uma vez que os Strings produzidos por show so normalmente
possveis de serem lidos por read.
A Figura 5.1 mostra um resumo das principais classes incorporadas Biblioteca padro de
Haskell e a relao de herana existente entre elas, baseadas na referncia [36]. importante
salientar que muitas outras classes so continuamente incorporadas a esta biblioteca.
131
Show
Eq showPrec Functor
show fmap
(==) (/=)
showList
Monad
Ord Num (>>=) (>>)
compare (+) () (*) return
negate
(<) (<=) (>=) (>) abs signum fail
max min
fromInteger
Ix MonadPlus
mZero
range
index mPlus
Real Fractional
inRange
rangeSize toRational (/)
recip
fromRational Floating
fromDouble
pi
Enum exp log sqrt
(**) logBase
succ pred sin cos tan
toEnum
fromEnum RealFrac sinh cosh tanh
asinh acosh atanh
enumFrom properFraction
enumFromThen truncate
enumFromTo round
enumFromThenTo ceiling
floor
RealFloat
floatRadix
floatDigits
floatRange
Integral Read decodeFloat
quot rem div mod readsPrec encodeFloat
quotRem divMod readList exponent
even odd significand
toInteger scaleFloat
isNaN isInfinite
isDenormalized
Bounded isNegativeZero
isIEEE
minBound maxBond atan2
J foram vistas vrias formas de modelar dados em Haskell. Vimos as funes de alta ordem,
polimorfismo, sobrecarga e as classes de tipos (type class). Vimos tambm que os dados podem
ser modelados atravs dos seguintes tipos:
Tipos bsicos (primitivos): Int, Integer, Float, Double, Bool, Char e listas.
Tipos compostos: tuplas (t1 , t2 , ..., tn ) e funes (t1 > t2 ), onde t1 e t2 so tipos.
Estas facilidades j mostram um grande poder de expressividade e de abstrao oferecidos
pela linguagem. No entanto, Haskell oferece uma forma especial para a construo de novos tipos
de dados visando modelar situaes peculiares. Como exemplo destas situaes podemos citar,
entre outras:
tipos cujos elementos sejam nmeros ou Strings. Por exemplo, uma casa em uma rua pode
ser identificada por um nmero ou pelo nome da famlia - em alguns pases isto normal;
as estruturas de rvores;
as estruturas de grafos.
Para construir um novo tipo de dados, usamos a declarao data que descreve como os ele-
mentos deste novo tipo so construdos. Cada elemento nomeado por uma expresso formulada
em funo dos construtores do tipo. Alm disso, nomes diferentes denotam elementos tambm
distintos. Atravs do uso de pattern matching sobre os construtores, projetam-se operaes que
132
geram e processam elementos do tipo de dados escolhidos pelo programador. Os tipos assim
descritos, no as operaes, so chamados tipos concretos [46, 4] e so modelados em Haskell
atravs dos tipos algbricos.
Um tipo algbrico mais uma facilidade que Haskell oferece, proporcionando ao programador
maior poder de abstrao, necessrio para modelar estruturas de dados complexas. Outras
possibilidades tambm existem, como por exemplo, os tipos abstratos de dados a serem vistos
mais adiante. A sintaxe da declarao de um tipo algbrico de dados
Isto significa que os construtures de valores podem ser tratados como funes que criam e
retornam uma nova publicao.
O tipo enumerao
O tipo enumerao, (enum), implementado nas linguagens C e C++ mantm alguma
semelhana com os tipos algbricos implementados em Haskell, apesar de ter alguma diferena.
Nas linguagens C e C++, os valores dos tipos enumerao so valores inteiros, podendo ser
133
utilizados em qualquer contexto onde um valor inteiro seja esperado, representando uma fonte
de possv eis bugs indesejveis. Este no o caso dos tipos algbricos em Haskell. Por exemplo,
em C, o tipo enumerao dia_util pode ser definido da seguinte forma:
Este tipo pode ser definido, em Haskell, como um tipo algbrico, com a vantagem da ausncia
dos bugs citados. Seno vejamos:
struct circulo {
struct ponto centro;
float raio;
};
struct retangulo {
struct ponto canto;
float base;
float altura;
};
struct figura {
enum forma tipo;
union {
struct circulo meucirculo;
struct retangulo meuretangulo;
}fig_gemetrica;
};
Neste exemplo, a unio pode conter dados vlidos para uma figura do tipo circulo ou para
uma figura do tipo retangulo. Temos de usar o campo tipo do tipo enum forma para indicar
explicitamente que tipo de valor est atualmente armazenado na unio. No entanto, o campo
tipo pode ser atualizado sem que a unio tambm seja, ou ento a uno pode ser atualizada
sem que o campo tipo tambm seja. Isto implica em insegurana na linguagem porque um valor
que seja um crculo pode ser usado como se fosse um retngulo e vice-versa. A verso em Haskell
para este cdigo dramaticamente menor e segura. Seno vejamos:
134
data Ponto = (Float, Float)
Se for criado um valor do tipo Figura usando o construtor de valores Circulo, no possvel
utiliz-lo, acidentalmente, como um valor construdo pelo construtor de valores Retangulo.
Produtos de tipos
J foi visto que um novo tipo de dado criado utilizando a declarao data. Haskell tambm
permite a criao de sinnimos para dar um significado mais claro a algum tipo, utilizando para
isso a declarao type. Tambm sabido que um produto de tipos um novo tipo de dados,
cujos valores so construdos com mais de um construtor. Por exemplo,
A leitura de um valor do tipo Gente deve ser feita da seguinte forma: para construir um
valor do tipo Gente, necessrio suprir um construtor de valor, Pessoa, juntamente com dois
campos, digamos, n do tipo Nome e outro, digamos i, do tipo Idade. O elemento formado ser
Pessoa n i. O construtor Pessoa funciona como uma funo aplicada aos outros dois objetos,
n e i. Exemplos de valores deste tipo podem ser:
Podemos formar uma funo mostraPessoa que toma um valor do tipo Gente e o mostra na
tela:
A aplicao da funo mostraPessoa (Pessoa "John Lennon", 50) ser respondida por
Neste caso, o tipo Gente tem o nico construtor, Pessoa, que utiliza dois valores dos tipos
Nome e Idade, para formar um valor do tipo Gente. O valor Pessoa n a pode ser interpretado
como sendo o resultado da aplicao de uma funo, Pessoa, aos argumentos n e a, ou seja,
Pessoa :: Nome > Idade > Gente.
O tipo para Gente poderia tambm ser definido como uma tupla, ou seja, como o tipo
Existem vantagens e desvantagens nesta verso, no entanto, senso comum que ela deve ser
evitada. Os motivos so baseados na legibilidade, porque valores construdos desta forma nada
indicam sobre a relao mnemnica que deve existir entre um tipo e seus valores.
Finalmente, ao se introduzir um tipo algbrico, podemos desejar que ele faa parte de de-
terminadas classes de tipos. Esta funo provida pela declarao deriving mostrada nas
declaraes anteriores.
Exerccios:
135
1. Sendo a Estao = {Primavera, Vero, Outono, Inverno} e Tempo = {Quente, Frio}, defina
a funo tempo :: Estacao > Tempo de forma a usar guardas em vez de pattern
matching.
2. Defina o tipo Meses como um tipo algbrico em Haskell. Faa uma funo que associe um
ms a sua Estao. Coloque ordenao sobre o tipo.
3. Defina uma funo que calcule o tamanho do permetro de uma forma geomtrica do tipo
Forma, definido no texto desta sub-seo.
4. Adicione um construtor extra ao tipo Forma para tringulos e estenda as funes area e
perimetro (exerccio anterior) para incluir os tringulos.
Podemos usar o mesmo nome para o construtor do tipo e para o construtor de valores, j que
eles representam objetos distintos. Por exemplo, perfeitamente legal a construo do tipo data
Pessoa = Pessoa Nome Idade.
Exemplo. Uma expresso aritmtica simples que envolve apenas adies e subtraes de
valores inteiros pode ser modelada atravs de sua BNF. Usando um tipo algbrico podemos
modelar uma expresso da seguinte forma:
data Expr = Lit Int |Add Expr Expr |Sub Expr Expr
Esta definio primitiva recursiva, ou seja, existe uma definio para um caso base (Lit n)
e uma definio recursiva para o passo indutivo. Por exemplo, uma funo que imprime uma
expresso pode ser feita da seguinte maneira:
Exerccios:
2. Calcule:
136
eval (Lit 67)
eval (Add (Sub (Lit 3) (Lit 1) (Lit 3))
mostraExpressao (Add (Lit 67) (Lit (-34))).
4. possvel estender o tipo Expr para que ele contenha expresses condicionais do tipo IF
b e1 e2, onde e1 e e2 so expresses e b uma expresso booleana, um membro do tipo
BExp.
por recurso e estenda a funo show para mostrar o tipo re-definido para expresses.
As definies de tipos algbricos podem conter tipos variveis como t, u, etc. Por exemplo,
e podemos ter
137
Par 2 3 :: Pares Int
Par [ ] [3] :: Pares [Int]
Par [ ] [ ] :: Pares [t]
Podemos construir uma funo que teste a igualdade das duas metades de um par:
5.4 rvores
As listas so consideradas estruturas de dados lineares porque seus tens aparecem em uma
seqncia pr-determinada, ou seja, cada tem tem, no mximo, um sucessor imediato. Por
outro lado, as estruturas de dados, conhecidas como rvores, so estruturas no lineares porque
seus tens podem ter mais de um sucessor imediato.
As rvores podem ser utilizadas como representaes naturais para quaisquer formas de dados
organizados hierarquicamente, por exemplo, as estruturas sintticas das expresses aritmticas
ou funcionais. Na realidade, as rvores provem uma generalizao eficiente das listas como
formas de organizao e recuperao de informaes.
Existem diversos tipos de rvores, classificadas de acordo com a forma de sua bifurcao, com
a localizao de armazenamento das informaes, com o relacionamento entre estas informaes,
ou ainda, de acordo com o objetivo da rvore criada. Nesta seo, sero vistas apenas algumas
destas definies.
Como o nome indica, uma rvore binria uma rvore que tem, no mximo, duas bifurcaes
para os seus tens. Podem existir vrias definies para estas rvores, no entanto, aqui ser
adotada a seguinte:
Esta a definio de uma rvore binria onde cada elemento ou um n externo (uma
Folha com um valor do tipo t) ou um n interno, sem um valor armazenado, mas com duas
sub-rvores binrias como sucessoras para este n.
Uma sequncia de nmeros inteiros pode ser representada por vrias rvorres binrias distin-
tas. Por exemplo, a partir da seqncia de nmeros inteiros 14, 09, 19 e 51 podem ser geradas
5 rvores binrias: 14 (09 (19 51)), 14 ((09 19) 51), (14 09) (19 51), (14 (09 19)) 51 e ((14 09)
19) 51, respectivamente mostradas graficamente na Figura 5.2.
Existem vrias medidas importantes das rvores em geral. Duas delas so a altura e o tamanho.
O tamanho de uma rvore se refere a quantidade de ns que detm informaes. No caso da
138
14 14 51 51
09 14 09 19 51 14 19
51
19 51 09 19 09 19 14 09
a) b) c) d) e)
Figura 5.2: Representao grfica das rvores binrias geradas pela seqncia 14, 09, 19, 51.
rvore binria definida desta forma, s existem informaes armazenadas nas folhas. Portanto,
o tamanho desta rvore a quantidade de suas folhas, tendo o mesmo papel que a funo length
exerce sobre as listas. Sua definio, em Haskell, :
Por outro lado, a altura de uma rvore definida como a maior distncia, em nveis, entre a
raiz e qualquer folha da rvore. Sua definio, em Haskell, a seguinte:
onde a funo max predefinida em Haskell. Uma outra operao importante que pode ser
aplicada s rvores binrias transform-las em listas. Sua definio a seguinte:
J foi observado que nas rvores binrias definidas desta forma um n ou uma folha ou
tem duas bifurcaes. Nestas rvores, existe uma relao importante entre a quantidade de ns
internos e externos, onde o nmero de ns externos sempre igual ao nmero de ns internos
mais 1. Os ns internos podem ser contados pela seguinte funo:
Uma outra caractertica importante das rvores a profundidade de cada um de seus ns,
que dada pela distncia, em nveis, que cada n tem da raiz da rvore. A profundidade da raiz
da rvore zero. A profundidade da rvore a maior das profundidades de suas folhas. Sua
definio a seguinte:
Diz-se que uma rvore binria perfeita se todas as suas folhas tiverem a mesma profundi-
dade, ou seja, se estiverem no mesmo nvel. Na Figura 5.2, mostrada anteriormente, apenas a
139
rvore representada na letra c) uma rvore binria perfeita. O tamanho de uma rvore binria
perfeita sempre uma potncia de 2 e existe exatamente uma rvore binria perfeita para cada
potncia de 2. Duas rvores binrias com o mesmo tamanho, no tm, necessariamente, a mesma
altura. Mesmo assim, estas medidas esto sempre relacionadas. O resultado a seguir verificado
em todas as rvores binrias finitas. Sua formulao a seguinte:
altura a < tamanho a 2altura a
A demonstrao desta propriedade feita utilizando o PIF (Princpio de Induo Finita)
sobre a altura h. A base da induo feita quando a rvore tem altura h = 0, ou seja, para
uma Folha a e o passo indutivo realizado admitindo-se que seja verdade para uma rvore do
tipo No ae ad e altura h, verificando a validade para uma rvore de altura h + 1.
Prova:
tamArvBin (N o ae ad)
= tamArvBin ae + tamArvBin ad pela definio de tamArvBin
2(altArvBin ae) + 2(altArvBin ad) pela hiptese de induo
2h + 2h sendo h = max (altArvBin ae) (altArvBin ad)
= 2(h + 1)
Concluso: como a assertiva vlida para o caso (Folha a) e para o caso (No ae ad), ento ela
vlida para toda rvore binria finita.
Dada qualquer lista finita xs de tamanho n, possvel construir uma rvore a com
arvBintolista a = xs e altArvBin a = dlog ne
onde dxe representa o menor inteiro que supera x.
Esta rvore tem uma altura mnima para a lista xs. Normalmente existem mais de uma
rvore de altura mnima para uma dada seqncia de valores. Estas rvores so importantes
porque o custo de recuperao de um valor mnimo, mesmo no pior caso.
Uma rvore de altura mnima pode ser obtida pela diviso da lista de entrada em duas
metades e, recursivamente, obtendo-se rvores de alturas mnimas para estas metades. A funo
fazArvBin, definida a seguir, constri esta rvore:
140
5.4.3 rvores binrias aumentadas
As rvores binrias definidas na sub-seo anterior se caracterizam pelo fato de suas informaes
serem armazenadas apenas nas folhas. No entanto, possvel definir uma rvore binria em que
os ns internos tambm armazenem informaes. Estas rvores tm aplicaes importantes e
so conhecidas como rvores binrias aumentadas.
No nosso caso, vamos aumentar a rvore binria, colocando em cada n interno a informao
dos tamanhos das subrvores que eles representam. Para isto, vamos definir um novo tipo de
rvore binria, que ser denotada por ArvBinA t.
A definio da funo fazArvBin tambm deve ser modificada para construir uma rvore
binria aumentada com altura mnima a partir de uma lista xs. Sua definio passa a ser:
A funo arvtolista, que transforma uma rvore binria em uma lista, tambm tem de
ser modificada para transformar uma rvore binria aumentada em uma lista. Ela deve ter a
seguinte implementao:
Podemos agora implementar uma funo recupera que retorna o valor de um determinado
n da rvore. Isto pode ser feito transformando-se a rvore em uma lista, aplicando em seguida
a funo !! que recupera o k-simo valor desta lista.
141
A funo recupera indexa a rvore da mesma forma que a funo !! indexa uma lista, ou
seja, ela retorna o valor especificado em um n, enquanto !! recupera o k-simo valor de uma
lista. Podemos tambm usar a relao a seguir que verdadeira sobre listas:
Esta nova definio, recupABA, usa a informao do tamanho da subrvore que est ar-
mazenada nos ns internos para controlar a busca por um elemento, em uma dada posio. Esta
implementao mais eficiente que a anterior que transforma a rvore toda em uma lista e depois
usa !! para recuperar um valor e esta busca proporcional ao tamanho da lista.
Exerccios (baseados em Richard Bird [4]).
2. Prove que o nmero de folhas em uma rvore binria sempre igual a 1 mais o nmero de
ns internos.
4. A rvore aumentada tambm pode ser construda inserindo-se em cada n interno a infor-
mao da profundidade deste n. Faa esta definio.
As rvores binrias tambm podem ser definidas levando em considerao a existncia de uma
rvore nula. Este tipo de rvore conhecida como rvore de busca binria. Sua definio pode
ser feita da seguinte forma:
Este o exemplo de uma declarao de tipo de dado que utiliza um contexto, ou seja, a
rvore do tipo ArvBusBin t definida para tipos t que sejam instncias da classe Ord. Isto
significa que todos os valores dos ns internos desta rvore podem ser comparados pelas relaes
maior, maior ou igual, menor, menor ou igual e igual. Como pode ser observado, esta definio
no diferencia ns internos de externos porque no contempla uma definio de Folha e as in-
formaes armazenadas nos ns aparecem entre as subrvores componentes. O construtor Nil
142
representa a rvore vazia ou nula. Este novo tipo de rvore tambm conhecida como rvore
binria rotulada. As funes arvtolista e arvBintolista, definidas anteriormente, que trans-
formam as rvores binrias e binrias aumentadas em listas podem ser facilmente modificadas
para refletir as alteraes da nova definio. Ela ser definida de forma a retornar a lista orde-
nada dos rtulos da rvore, sendo este o motivo pelo qual os elementos da nova rvore devem
pertencer a tipos cujos valores possam ser ordenados. Neste caso, os elemantos sero ordenados
na forma in-ordem.
Como o nome sugere, as rvores de busca binrias so utilizadas para realizar buscas de
forma eficiente. A funo membroABB que determina se um determinado valor aparece, ou
no, como o rtulo de algum n pode ser definida da seguinte forma:
No pior caso, o custo de avaliar membroABB x abb proporcional altura de abb, onde
a definio de altArvBin deve ser modificada para altABB, da seguinte forma:
A diferena entre as funes fazABB e fazArvBin que fazABB no garante que a rvore
resultante tenha altura mnima. Por exemplo, o valor de x na segunda equao de fazABB
pode ser menor que qualquer elemento de xs, possibilitando que fazABB construa uma rvore
143
em que a subrvore esquerda seja nula. Enquanto a definio de fazArvBin pode ser executada
em tempo linear, a implementao mais eficiente de fazABB requer, pelo menos, n log n passos,
quando ambas so aplicadas a uma lista de tamanho n.
A definio da funo fazABB pode ser utilizada para implementar uma funo sort para
ordenar listas.
Se for eliminada a rvore intermediria desta definio, ela se transforma em uma definio
da funo quicksort, bastante utilizada na ordenao de listas.
As rvores de busca binria podem ser utilizadas na implementao eficiente dos conjuntos como
um tipo de dados abstratos a ser definido no prximo Captulo. Para isto necessrio definir as
funes de insero e remoo de tens nestas rvores. A funo de insero ser insere:
Nesta definio, pode-se observar que, se um tem j estiver na rvore, ele no ser mais
includo. A funo de remoo um pouco mais complexa porque ao se retirar um n interno
da rvore necessrio juntar as duas sub-rvores deste n. A funo de remoo ser definida
como remove:
144
where k = minArv (No ade y add)
Na Figura 5.3 est mostrado um exemplo da funo junta em ao. Na rvore inicial, o
rtulo da raiz (40) ser removido deixando as subrvores ae e ad isoladas. Elas devero ser
juntas para formar a nova rvore e, para isso, o menor rtulo da subrvore ad, 45, ser movido
de sua posio na sub-rvore ad e ocupar o lugar da raiz.
Vamos agora definir um outro tipo de rvore binria conhecida como rvore heap binria, cujo
tipo ser ArvHB.
As rvores heap binrias podem ser utilizadas para propsitos diferentes dos indicados para
as rvores de busca binrias, ou seja, para operaes que no sejam de remoo, insero ou
145
pertinncia. As rvores heap binrias so mais adequadas para tarefas, por exemplo, encontrar
o menor rtulo em uma heap que pode ser feito em tempo constante. Esta operao realizada
em uma rvore de busca binria requer tempo proporcional distncia ao n mais esquerda.
Enquanto a juno de duas rvores de busca binrias uma tarefa complexa, a juno de duas
rvores heaps binrias simples.
Construo de heaps
Uma forma de construir uma rvore heap binria definir uma nova verso da funo fazABB,
transformando-a na funo fazHB.
Deve ser notado que, apesar do programa ser bastante pequeno, ele no representa a forma
mais eficiente de construo de heaps. A idia definir fazHeap como uma composio de
funes auxiliares:
A funo desliza toma um rtulo e duas rvores heap binrias e constri uma nova rvore
heap binria, deslizando o rtulo at que a propriedade de heap seja estabelecida.
Finalmente, podemos usar rvores heap binrias para obter uma nova implementao para o
algoritmo de ordenao, conhecido como heapsort:
146
1. Defina uma funo permuta que troca a ordem de uma unio. permuta :: Uniao t u
> Uniao u t. Qual ser o efeito da aplicao (twist . twist)?
2. Uma forma de construir uma rvore binria a partir de uma lista [x0 , x1 , x2 , . . .] feita
da seguinte forma: x0 ser o rtulo da raiz da rvore, x1 e x2 sero os rtulos dos filhos,
x3 , x4 , x5 e x6 sero os rtulos dos netos e assim sucessivamente. Esta uma forma de
diviso de uma lista em sub-listas cujos tamanhos sejam potncias de 2 pode ser imple-
mentada atravs da funo niveis, definida por
3. Podemos tambm definir rvores mais gerais com uma lista arbitrria de sub-rvores. Por
exemplo,
data ArvoreGeral t = Folha t | No [ArvoreGeral t]
defina funes para:
Para construir bons programas, necessrio especificar o que o programa deve fazer no caso de
acontecer algumas situaes anmalas. Estas ocorrncias so conhecidas como excees, normal-
mente indesejveis, e podem ser de vrios tipos: a tentativa de diviso por zero, clculo de raiz
quadrada de nmero negativo ou a aplicao da funo fatorial a um nmero negativo, tentativa
de encontrar a cabea ou a cauda de uma lista vazia, entre outros.
Existem basicamente trs tcnicas paraa resolver estes tipos de problemas, conhecidas como
tcnicas de tratamento de erros. A soluo mais simples exibir uma mensagem informando o
tipo da exceo e parar a execuo do programa. Isto pode ser feito atravs de uma funo de
erro.
Uma tentativa de avaliar a expresso error "Circulo com raio negativo" resultar na
mensagem
147
5.5.1 Valores fictcios
A funo tail construda para retornar a cauda de uma lista finita no vazia e, se a lista for
vazia, reportar esta situao com uma mensagem de erro e parar a execuo. Ou seja,
Desta forma, todas as listas passam a ter uma resposta para uma solicitao de sua cauda,
seja ela vazia ou no. Se a lista no for vazia, sua cauda ser reportada normalmente. Se a lista
for vazia, o resultado ser o valor fictcio
. De forma similar, a funo de diviso de dois nmeros inteiros pode ser feita da seguinte forma,
envolvendo o caso em que o denominador seja zero:
Para estes dois casos, a escolha dos valores fictcios bvia. No entanto, existem casos em
que esta escolha no simples, nem mesmo possvel. Por exemplo, na definio de uma funo
que retorne a cabea de uma lista. Qual deve ser o valor fictcio a ser adotado neste caso?
Para resolver excees deste tipo, temos de recorrer a um artifcio mais elaborado. A soluo
adotada definir a funo hd, acrescentando mais um parmetro.
Esta tcnica mais geral e consiste na definio de uma nova funo para o caso de ocorrer
uma exceo. Em nosso caso, a funo hd de um argumento foi modificada para ser aplicada a
dois argumentos. De maneira geral, constri-se uma funo com uma definio para o caso de
surgir uma exceo e outra definio para o caso em que ela no ocorra.
fErr y x
|cond = y
|otherwise = f x
Esta tcnica funciona bem em muitos casos. O nico problema que no reportada nenhuma
mensagem informando a ocorrncia incomum. Uma outra abordagem, mais elaborada e desejvel,
consiste em processar a entrada indesejvel.
148
5.5.2 Tipos de erros
As tcnicas anteriores para se tratar as excees se baseiam no retorno de valores fictcios, caso
elas aconteam. No entanto, em Haskell, possvel se adotar uma outra bem mais elaborada.
Baseia-se no retorno de um valor erro como resultado. Isto feito atravs do tipo Maybe.
fErr x
| cond = Nothing
| otherwise = Just (f x)
O resultado destas funes agora no so do tipo de sada original, digamos t, mas do tipo
Maybe t. Este tipo, Maybe, nos permite processar um erro. Podemos fazer duas coisas com
ele:
1. podemos repassar o erro atravs de uma funo, que o efeito da funo mapMaybe, a
ser vista a seguir ou
2. podemos segurar a exceo, que o papel da funo maybe, tambm definida a seguir.
Para segurar um erro e resolv-lo, deve-se retornar um resultado do tipo b, a partir de uma
entrada do tipo Maybe a. Neste caso, temos duas situaes:
no caso Nothing, temos de apresentar o valor do tipo b que vai ser retornado.
A funo de alta ordem que realiza este objetivo maybe, cujos argumentos, n e f, so
usados nos casos Nothing e Just, respectivamente.
149
Vmos acompanhar as funes mapMaybe e maybe em ao, nos exemplos que seguem.
No primeiro deles, a diviso por zero nos retorna Nothing que vai sendo empurrado para a
frente e retornar o valor 56.
No caso da diviso ser normal, o valor a ser retornado Just 9. Este resultado multiplicado
por 3 e maybe, no nvel externo, adiciona 1 e remove o Just.
A vantagem desta tcnica que podemos definir o sistema sem gerenciar os erros e depois
adicionar um gerenciamento de erro usando as funes mapMaybe e maybe juntamente com
as funes modificadas para segurar o erro. Separar o problema em duas partes facilita a soluo
de cada uma delas e do todo.
Exerccio.
Defina uma funo process :: [Int] > Int > Int > Int de forma que process l n
m toma o n-simo e o m-simo elementos de uma lista l e retorna a sua soma. A funo deve
retornar zero se quaisquer dos nmeros n ou m no forem ndices da lista l. Para uma lista de
tamanho p, os ndices so: 0, 1, ..., p-1, inclusive.
Vamos iniciar nossa discusso avaliando a expresso quadrado (5 + 7), onde a funo quadrado
foi definida no Captulo 3. Vamos seguir duas seqncias de reduo possveis para esta expresso.
A primeira delas a seguinte:
quadrado (5 + 7)
= quadrado 12 -- usando a definicao do operador +
= 12 x 12 -- usando a definicao da funcao quadrado
= 144
quadrado (5 + 7)
= (5 + 7) * (5 + 7) -- pela definicao da funcao quadrado
= 12 * (5 + 7) -- pela definicao do operador +
= 12 * 12 -- pela definicao do operador +
= 144
Estas duas seqncias de redues ilustram duas polticas de escolha de reduo, conhecidas
como innermost e outermost, respectivamente. Na primeira delas, em cada passo de reduo, foi
escolhido o redex mais interno, ou seja, o redex que no continha qualquer outro redex interno a
150
ele. J na segunda seqncia de redues, optou-se pela escolha do redex mais externo, ou seja,
um redex que no estava contido em qualquer outro.
Vamos analisar mais um exemplo, utilizando as polticas de seqncias de reduo, agora
sendo aplicadas expresso fst (quadrado 5, quadrado 7). Usando a poltica innermost,
teremos a seguinte seqncia:
Nesta seqncia de redues, foram utilizados 5 passos at chegar forma mormal. Nos
primeiros dois passos desta seqncia, verifica-se que existiam duas possibilidades de escolha: a
funo quadrado 5 ou a funo quadrado 7. Apesar de ambos obedecerem poltica innermost,
a escolhe recaiu sobre o redex mais esquerda, conhecido como leftmost.
Se, por outro lado, tivssemos escolhido a poltica outermost, a seqncia de redues seria
a seguinte:
Usando esta poltica de reduo, foram utilizados apenas 3 passos at atingir a forma normal
da expresso inicial porque a avaliao da funo quadrado 7 foi evitada, apesar de, em ambas
as seqncias, o resultado ser o mesmo.
Qual a diferena entre estas duas polticas de redues? Para responder a esta questo, vamos
supor que a funo quadrado 7 fosse indefinida na expresso inicial. Neste caso, a primeira
seqncia de redues no terminaria, ao passo que a segunda, sim. Isto nos permite inferir que
se as duas seqncias de redues atingem forma normal, elas chegam ao mesmo resultado e
que, em alguns caso, a utilizao da poltica innermost pode no atingir sua forma normal.
Resumidamente, se uma expresso tiver uma forma normal, ela pode ser computacionalmente
atingida utilizando a poltica outermost, ou seja, pode-se construir um programa computacional
capaz de encontrar sua forma normal. Por este motivo, a poltica de redues outermost
tambm conhecida como ordem normal. O leitor deve recorrer ao Captulo 2 deste estudo e
reanalisar os teoremas de Church-Rosser que ali so aplicados ao -clculo.
Apesar desta caracterstica importante, devemos observar que, no primeiro exemplo mostrado
nesta seo, foram necessrios 4 passos, enquanto que utilizando a poltica innermost foram
necessrios apenas 3. No entanto, se na expresso a ser avaliada existirem funes onde algum
argumento ocorra de forma repetida possvel que este argumento seja ligado a uma expresso
grande e, neste caso, a poltica de reduo innermost apresenta um desempenho bem menor.
Na Introduo deste nosso estudo, foi afirmada a utilizao de grafos de reduo na rep-
resentao de expresses, em vez de rvores. Nos grafos, as sub-expresses de uma expresso
podem ser compartilhadas o que no possvel utilizando rvores. Por exemplo, a expresso
(5 + 7) (5 + 7) pode ser representada pelo grafo da Figura 5.4.
Neste caso, cada ocorrncia da subexpresso (5 + 7) representada por um arco que um
ponteiro para uma mesma instncia de (5+7). A representao de expresses como grafos significa
151
( * ) (5 + 7)
Figura 5.4: Representao grfica da funo * em ao.
que as sub-expresses que ocorrem mais de uma vez so compartilhadas, portanto reduzidas a
uma nica.
Uma observao importante que na utilizao de grafos de reduo a reduo outermost
nunca gasta mais passos que a reduo innermost. A reduo outermost em grafos de reduo
referenciada normalmente como lazy evaluation e a reduo innermost como eager evaluation.
As sub-expresses compartilhadas tambm podem acontecer em definies locais. Por exem-
plo, seja a definio da funo raizes a seguir,
onde o primeiro passo para a reduo da aplicao raizes 1 5 3 pode ser mostrado pelo grafo
da Figura 5.5.
eqFunc1 a b = a + b --linha 1
eqFunc1 (9 - 3) (eqFunc1 34 (9 - 3)) --linha 2
= (9 - 3) + (eqFunc1 34 (9 - 3)) --linha 3
= 6 + (eqFunc1 34 6) --linha 4
= 6 + (34 + 6) --linha 5
= 6 + 40 --linha 6
= 46 --linha 7
Nas linhas 2 e 3 deste script, a sub-expresso (9 - 3) ocorre duas vezes. Neste caso, o avaliador
de Haskell avalia esta sub-expresso apenas uma vez e guarda o resultado, na expectativa de que
152
ela seja novamente referenciada. Na segunda ocorrncia da sub-expresso, ela j est avaliada.
Como no -clculo, a ordem de avaliao sempre da esquerda para a direita, ou seja, leftmost-
outermost. Na avaliao da funo eqFunc2, a seguir, o segundo argumento (eqFunc1 34 3)
no avaliado porque ele no necessrio para a aplicao da funo eqFunc2.
eqFunc2 a b = a + 32
eqFunc2 (10 * 40) (eqFunc1 34 3)
= (10 * 40) + 32
= 400 + 32
= 432
Nas sub-sees a seguir so mostrados alguns exemplos de como as listas so construdas, levando
em considerao o mecanismo de avaliao lazy. Em particular, as listas potencialmente infinitas
apenas so possveis por causa da existncia deste mecanismo. interessante observar que
algumas definies so de fato bastante criativas, o que torna as linguagens funcionais bastante
atrativas aos programadores que gostam de utilizar formas inusitadas e criativas na construo
de programas.
Algumas aplicaes que usam a construo de listas por compreenso s podem ser comple-
tamente entendidas se conhecermos o sistema de avaliao, principalmente quando envolver a
criao de listas potencialmente infinitas.
Uma expresso ZF uma expresso com a seguinte sintaxe:
[e | q1 , ..., qk ], onde e uma expresso e cada qi um qualificador que tem uma entre duas
formas:
1. um gerador: p<- lExp, onde p um padro e lExp uma expresso do tipo lista ou
2. um teste: bExp, onde bExp uma expresso booleana, tambm conhecida como guarda.
Exemplos
Deve ser observado, neste ltimo exemplo, a ordem em que as triplas foram geradas. Ele
pega o primeiro nmero do primeiro gerador, em seguida o primeiro gerador do segundo, depois
percorre todos os valores do terceiro gerador. Quando todos os elementos do terceiro gerador se
exaurirem o mecanismo volta para o segundo gerador e pega agora o seu segundo elemento e vai
novamente percorrer todo o terceiro gerador. Aps se exaurirem todos os elementos do segundo
gerador ele volta para o segundo elemento do primeiro gerador e assim prossegue at o final.
Seqncia de escolhas
153
Para mostrar a seqncia de atividades, vamos utilizar a notao do -clculo para -
redues. Seja e uma expresso, portanto e{f/x} a expresso e onde as ocorrncias de x
so substitudas por f. Formalmente, temos:
[e | v < [a1 , ..., an ], q2 , ..., qk ]
= [e{a1 /v} |, q2 {a1 /v}, ..., qk {a1 /v}] ++ ... ++ [e{an /v} | q2 {an /v}, ..., qk {an /v}].
Por exemplo, temos:
[(a, b) | a < l] {[2, 3]/l} = [(a, b) | a < [2, 3]] e
(a + sum(x)){(2, [3, 4])/(a, x)} = 2 + sum[3, 4] .
Regras de teste
Na utilizao desta sintaxe necessrio que algumas regras de aplicao sejam utilizadas.
Estas regras so importantes para entender porque algumas aplicaes, envolvendo a criao de
listas, no chegam aos resultados pretendidos.
[e | T rue, q2 , ..., qk ] = [e | q2 , ..., qk ],
[e | F alse, q2 , ..., qk ] = [ ]
[e |] = [e] .
Exemplos
Vamos mostrar a seqncia de operaes em alguns exemplos, para ficar mais claro:
Exerccio. Faa o clculo da expresso [a + b | a <- [1..4], b <- [2..4], a < b].
Suponhamos que seja necessrio encontrar a soma das quartas potncias dos nmeros 1 a n. Os
passos a seguir sero necessrios para resolver este problema:
elevar quarta potncia cada nmero da lista, gerando a lista [1, 16, ..., n4 ] e
154
= sum ((1^4) : map (^4) [2..n])
= sum (1 : map (^4) [2..n])
= 1 + sum (map (^4) [2..n])
= 1 + sum ((^4)(2: [3..n])
= 1 + sum (((^4) 2) : map (^4) [3..n])
= 1 + sum ((2^4) : map (^4) [3..n]))
= 1 + sum (16 : map (^4) [3..n])
= 1 + 16 + sum (map (^4) [3..n]) . . .
Deve ser observado que a lista no criada toda de uma s vez. To logo uma cabea
encontrada, toma-se a sua quarta potncia e em seguida aplicada a soma com algum outro
fator que vai surgir em seguida.
uns :: [Int]
uns = 1 : uns -- a lista infinita de 1s [1, 1, ..]
somaPrimDoisUns uns
= somaPrimDoisUns (1 : uns)
= somaPrimDoisUns (1 : 1 : uns)
= 1 + 1 = 2.
Apesar de uns ser uma lista infinita, o mecanismo de avaliao no precisa calcular toda a lista,
para depois somar apenas os seus primeiros dois elementos. Desta forma, assim que o mecanismo
encontrar estes elementos, a soma realizada e a execuo da funo termina. Mais exemplos, a
seguir:
Exemplos
155
crivo :: [Int] -> [Int]
crivo [ ] = [ ]
crivo (x : xs) = x : crivo [y | y <- xs, mod y x > 0]
3. A lista dos nmeros primos. A lista infinita dos nmeros primos pode ser definida a partir
do crivo de Eratstenes definido anteriormente.
primos :: [Int]
primos = crivo [2..]
4. (Baseado em [46]). Dada a lista infinita [a0 , a1 , a2 , . . .], construir a lista infinita
[0, a0 , a0 + a1 , a0 + a1 + a2 , . . .]. Para resolver este problema, vamos construir a funo
somaListas da seguinte forma:
Exerccio
Defina listas infinitas de fatorial e de Fibonacci.
Com os tipos algbricos tambm podemos realizar provas sobre a corretude ou no de alguns
programas, de forma similar s provas com outros tipos de dados. Por exemplo, reformulando a
definio de rvore binria, podemos ter
Para provar uma propriedade P(tr) para todas as rvores finitas do tipo Arvore t, temos
de analisar dois casos:
156
Esquema da prova:
O caso Nil:
Lado esquerdo Lado direito
map f (collapse Nil) collapse (mapTree f Nil)
= map f [ ] por (5) = collapse Nil por (3)
=[] por (1) =[] por (5)
Exerccios:
2. Para toda rvore finita de inteiros tr, sendo a funo quantos definida para verificar
quantas vezes um elemento ocorre em uma rvore, vale a equao a seguir. Defina quantos
e mostre a igualdade.
quantos tr a = length (filter (==a) (collapse tr)).
3. Prove que a funo permuta, definida anteriormente, tem a propriedade de que permuta
. permuta = id.
5.8 Resumo
Este Captulo foi dedicado ao estudo de vrios temas. No entanto o objetivo maior foi analisar
os tipos de dados complexos, em particular, os tipos algbricos de dados. Foi visto como eles
157
podem ser construdos em Haskell para que o leitor possa segu-los e compreender como eles
podem modelar problemas. Na realidade, programar simular problemas construindo modelos
para estes problemas, de forma que estes modelos possam ser processados por um computador,
emitindo solues que possam ser interpretadas como solues para estes problemas.
Para possibilitar o uso destes tipos de dados, uma gama de ferramentas foram construdas,
em Haskell. Entre elas a utilizao de mdulos, o mecanismo de avaliao lazy e as compreenses.
Dadas as possibilidades de construo de tipos que a linguagem oferece, o objetivo do Captulo
foi mostrar uma grande gama de problemas em cujas solues a linguagem Haskell pode ser
aplicada com sucesso.
A grande fonte de exemplos e exerccios mostrados neste Captulo, foram os livros de Simon
Thompson [46] e Richard Bird [4]. No entanto, um fonte importante de problemas a serem
resolvidos pode ser o livro de Steven Skiena [39] e os sites:
http://www.programming-challenges.com e
http://online-judge.uva.es.
As descries de rvores AVL, B, B+, B*, red-black e outros tipos podem ser encontradas
na bibliografia dedicada aos temas Algoritmos e Estruturas de Dados. O leitor aconselhado a
consultar.
Para quem deseja conhecer mais aplicaes de programas funcionais, o livro de Paul Hudak
[11] uma excelente fonte de estudo, principalmente para quem deseja conhecer aplicaes da
multimdia usando Haskell.
158
Captulo 6
6.1 Introduo
No Captulo introdutrio desta Apostila, afirmamos que a principal vantagem das linguagens es-
truturadas era a modularidade oferecida por elas e, por este motivo, as linguagens funcionais eram
indicadas como soluo para a crise do software dos anos 80. De fato, as linguagens funcionais
so modulares e Haskell oferece muitas alternativas para a construo de mdulos, obedecendo
s exigncias preconizadas pela Engenharia de Software para a construo de programas.
Para John Hughes [13], a modularidade uma caracterstica que as linguagens de progra-
mao devem apresentar para que sejam utilizadas com sucesso, na construo de grandes sis-
temas. Ele afirma que esta a caracterstica principal das linguagens funcionais, proporcionada
atravs das funes de alta ordem e do mecanismo de avaliao lazy.
A modularidade importante porque:
No entanto algumas preocupaes devem ser levadas em considerao para que estes benefcios
sejam de fato auferidos. Caso contrrio, em vez de facilitar, a modularizao pode pode ser
tornar inoportuna. Entre os cuidados preconizados pela Engenharia de Software em relao aos
mdulos, so citados:
159
cada mdulo deve ser auto-contido,
Nesta seo, fizemos um estudo rpido sobre a utilizao de mdulos em Haskell. O objetivo
mostrar as muitas possibiolidades que a linguagem oferece. A prtica da programao em
mdulos que vai dotar o programador da experincia e habilidade necessrias para o bom
desenvolvimento de programas.
Cada mdulo em Haskell constitui um arquivo. Isto significa que no devem existir um
arquivo em Haskell que contenha mais de um mdulo. Isto se deve ao fato de que o nome do
arquivo tem de ser o mesmo do mdulo, ambos iniciados com letra maiscula. Para declarar o
mdulo Formiga, devemos construir o arquivo Formiga.hs ou Formiga.lhs que deve conter
as seguintes declaraes:
module Formiga
( Formigas(..),
comeFormiga,
mataFormiga
) where
Se, ao contrrio, se deseja que nenhum nome do mdulo seja exportado, o que normalmente
no usual, pode-se escrever os parnteses sem nada dentro deles. Por exemplo,
160
6.2.1 Controles de exportao
Por default, tudo o que declarado em um mdulo pode ser exportado, e apenas isto, ou seja, o
que importado de outro mdulo no pode ser exportado pelo mdulo importador. Esta regra
pode ser exageradamente permissiva porque pode ser que algumas funes auxiliares no devam
ser exportadas e, por outro lado, pode ser muito restritiva porque pode existir alguma situao
em que seja necessrio exportar algumas definies declaradas em outros mdulos.
Desta forma, deve-se poder controlar o que deve, ou no, ser exportado. Em Haskell, isto
declarado da seguinte forma:
ou equivalentemente
module Abelha
(module Abelha, module Formiga) where . . .
A palavra module dentro dos parnteses significa que tudo dentro do mdulo exportado,
ou seja, module Abelha where equivalente a module Abelha (module Abelha) where.
Os mdulos, em Haskell, podem importar dados e funes de outros mdulos da seguinte forma:
Alm dos controles para exportao, Haskell tambm apresenta controles para a importao de
definies.
Neste caso, a inteno importar apenas o tipo Formigas do mdulo Formiga. Haskell
tambm prov uma forma explcita de esconder alguma entidade. Por exemplo,
161
Nesta declarao, a inteno esconder a funo comeFormiga. Se em um mdulo existir
um objeto com o mesmo nome de outro objeto, definido em um mdulo importado, pode-se
acessar ambos objetos usando um nome qualificado. Por exemplo, Formiga.urso o objeto
importado do mdulo Formiga e urso o objeto definido localmente. Um nome qualificado
construdo a partir do nome de um mdulo e do nome do objeto neste mdulo. Para usar um
nome qualificado, necessrio que se faa uma importao:
No caso dos nomes qualificados, pode-se tambm estabelecer quais tens vo ser exportados
e quais sero escondidos. Tambm possvel usar um nome local para renomear um mdulo
importado, como em
Alm do interpretador GHCi, a distribuio GHC inclui tambm um compilador de cdigo nativo,
ghc. Para compilar um arquivo fonte necessrio abrir uma janela para linha de comandos e
chamar o ghc com o nome do arquivo a ser compilado, da seguinte forma
ghc -c Abelha.hs
O parmetro -c informa ao compilador ghc que ele deve gerar apenas o cdigo objeto para
o arquivo Abelha.hs. Se esta opo for omitida, o compilador ir tentar gerar um programa
executvel e no o programa objeto, o que ir provocar um erro porque ainda no foi definida
a funo main que GHC, obrigatoriamente, chama para iniciar a execuo de um programa
isolado.
Aps o compilador GHC concluir seu trabalho, ele gera os arquivos Abelha.o e Abelha.hi.
O arquivo Abelha.o contm cdigo de mquina e o arquivo Abelha.hi um arquivo de interface,
onde GHC armazena informaes sobre os nomes exportados pelo mdulo.
Em todo sistema deve existir um mdulo chamado Main que deve ser coficado no arquivo
Main.hs e deve conter a definio da funo main. Em um sistema interpretado, como Hugs,
ele tem pouca importncia porque um mdulo sem um nome explcito tratado como Main.
Para o caso em voga, ele pode ser codificado da seguinte forma:
Aps ter compilado os mdulos com a diretiva -c, ou seja, aps terem sido criadas as interfaces
e os cdigos objetos dos mdulos, pode-se criar o arquivo executvel, que pode ter um nome
qualquer. Neste caso, foi escolhido o nome bicho para o programa executvel. Isto pode ser
feito da seguinte forma:
162
ghc -o bicho Main.hs Abelha.o
Como pode ser observado, a opo agora -o, que informa ao ghc que deve realizar a
linkedio ou lincagem do programa executvel. O ghc realiza esta tarefa e gera o arquivo
bicho.exe, sob o sistema operacional Windows, ou apenas bicho (sem extenso) sob o sistema
operacional Unix ou seus clones. Todos os arquivos a serem lincados devem constar na lista de
arquivos -o. Se algum arquivo for esquecido, o ghc reclama da presena de smbolos indefinidos,
indicando que alguma ou algumas definies no foram providas. Aps esta etapa, o programa
bicho.exe pode ser executado.
O leitor deve consultar o Apndice B para saber que passos devem ser seguidos para compilar
e executar programas codificados em Haskell, uma vez que existem muitas alternativas para a
compilao utilizando GHC. O que foi colocado aqui foi apenas para tornar o Captulo mais ou
menos autocontido, necessitando que o leitor veja o apndice citado para ter um controle maior
sobre o processo de compilao.
1. a representao do tipo e as operaes permitidas sobre eles esto contidas em uma nica
unidade sinttica e
2. a representao do tipo no visvel pelas unidades que o usam, ou seja, as nicas operaes
acessveis diretamente so as oferecidas na definio do tipo.
Este tipo de organizao permite que os programas sejam divididos em unidades lgicas que
podem ser compiladas separadamente, possibilitando que sejam feitas modificaes na represen-
tao e nas operaes do tipo, sem que isto implique na recompilao de um programa que seja
cliente deste tipo de dado. Ocultando a representao e as operaes aos clientes, permite que
apenas o implementador possa trocar esta representao e operaes, e isto significa confiabili-
dade.
163
Nas sees a seguir, sero feitas consideraes relacionadas aos tipos abstratos de dados
e as formas de implementao destes tipos em Haskell. Estas implementaes so analisadas
quanto aos desempenhos que elas apresentam. Para cada tipo abstrato de dados definido sero
analisadas verses com diferentes desempenhos. O Captulo tem incio com o estudo dos Mdulos
em Haskell.
Haskell, como linguagem funcional, prov um nvel de abstrao bem mais alto que as linguagens
imperativas. Este fato, implica que os programas funcionais sejam bem menores, em termos de
tamanho de cdigo, mais claros e mais rpidos de serem desenvolvidos que os programas imper-
ativos. Isto contribui para um melhor entendimento do algoritmo que est sendo implementado,
alm de possibilitar que solues alternativas sejam exploradas mais rapidamente.
J foi fortemente mencionado que Haskell usa o mecanismo de avaliao lazy como default.
No entanto, a linguagem tambm dispe de um mecanismo para que a avaliao de funes seja
feita de forma estrita, conhecida como eager evaluation, ou avaliao gulosa. Esta metodologia
de passagem de parmetros conhecida como passagem por valor, o que torna os programas
bastante rpidos e eficientes. O problema que a avaliao gulosa pode levar, em alguns casos,
a um loop infinito, fazendo com que o programa nunca pre. Isto garantido pelo teorema de
Church-Rosser, j analisado nos captulos 1 e 2. J com a avaliao preguiosa, se o programa
tiver uma forma normal ela ser encontrada usando esta tcnica de avaliao. No entanto,
esta forma de avaliao pode exigir mais passos de execuo que a avaliao gulosa. Alguns
compiladores utilizam um mecanismo de verificao da adequao deste mtodo em funes.
Este mecanismo conhecido como strictness anylising [32].
O problema que a anlise de desempenho pode dar resultados diferentes se for usada a
avaliao estrita ou a avaliao lazy. A anlise de desempenho em programas estritos relativa-
mente fcil, mas em programas lazy no tem se mostrado uma tarefa simples. Por este motivo,
a anlise da complexidade das funes ser feita de forma superficial e sem muita profundidade,
dada a dificuldade do tema e no ser este o objetivo deste estudo.
A funo reverse inverte a ordem dos elementos de uma lista de qualquer tipo. Ela pr-definida
em Haskell, e pode ser facilmente definida por
n1
X n(n 1)
(n 1) + (n 2) + (n 3) + . . . + 1 + 0 = i= O(n2 )
i=0
2
164
Assim, reverse precisa de um tempo proporcional ao quadrado do tamanho da lista a ser
invertida. Ser que existe outra forma de implementar reverse com um desempenho melhor?
A resposta sim, ou seja, possvel implement-la em um tempo proporcional ao tamanho da
lista. Vejamos como isto pode ser feito.
Imagine que se deseja inverter a ordem de uma pilha de livros. Para isto, necessrio retirar
o livro que se encontra no topo da pilha e coloc-lo ao lado, iniciando uma nova pilha de livros.
Este processo deve ser continuado, ou seja, retirando mais um livro do topo da pilha anterior e
colocando-o no topo da nova pilha, at que a pilha anterior fique vazia. Neste instante, a nova
pilha ser a pilha anterior em ordem inversa.
Este raciocnio ser aplicado na implementao da funo reverse. Sero usadas duas listas,
la (lista antiga) e ln (lista nova), para simular as pilhas de livros. necessrio tambm definir
uma funo auxiliar, revaux, que quando aplicada s duas listas, la e ln retira o elemento da
cabea da primeira, la, e o coloca como cabea da segunda, ln. A funo revaux termina sua
execuo quando la ficar vazia. Neste ponto, o resultado ser a segunda lista, ln. Em Haskell,
esta definio feita da seguinte forma:
A nova definio da funo reverse ser feita aplicando a funo revaux lista a ser invertida
e lista vazia como seus argumentos, ou seja:
O objetivo desta seo introduzir os tipos abstratos de dados (TAD) e o mecanismo provido por
Haskell para defin-los. De maneira geral, os tipos abstratos de dados diferem dos introduzidos
por uma declarao data, no sentido de que os TADs podem escolher a representao de seus
valores. Cada escolha de representao conduz a uma implementao diferente do tipo de dado
[46].
Os tipos abstratos so definidos de forma diferente da forma utilizada para definir os tipos
algbricos. Um tipo abstrato no definido pela nomeao de seus valores, mas pela nomeao
de suas operaes. Isto significa que a representao dos valores dos tipos abstratos no
conhecida. O que realmente de conhecimento pblico o conjunto de funes para manipular
o tipo. Por exemplo, Float um tipo abstrato em Haskell. Para ele, so exibidas operaes de
comparao e aritmticas alm de uma forma de exibio de seus valores, mas no se estabelece
como tais nmeros so representados pelo sistema de avaliao de Haskell, proibindo o uso de
165
casamento de padres. Em geral, o programador que usa um tipo abstrato no sabe como seus
elementos so representados. Tais tipos de abstrao so usuais quando mais de um programador
estiverem trabalhando em um mesmo projeto ou mesmo quando um mesmo programador estiver
trabalhando em um projeto no trivial. Isto permite que a representao seja trocada sem afetar
a validade dos scripts que usam o tipo abstrato. Vamos mostrar estes fundamentos atravs de
exemplos.
Vamos dar incio ao estudo dos tipos abstratos de dados atravs da implementao do tipo Pilha.
As pilhas so estruturas de dados homogneas onde os valores so colocados e/ou retirados
utilizando uma estratgia LIFO (Last In First Out). Informalmente, esta estrutura de dados
comparada a uma pilha de pratos, na qual s se retira um prato por vez e sempre o que est
no topo da pilha. Tambm s se coloca um prato por vez e em cima do prato que se encontra
no topo.
As operaes1 necessrias para o funcionamento de uma pilha do tipo Stack, juntamente com
seus tipos, formam a assinatura do tipo abstrato. No caso em voga, a assinatura composta das
seguintes funes:
Sero feitas duas implementaes do tipo pilha, sendo a primeira baseada em tipos algbricos
e a segunda baseada em listas.
push x s = Stk x s
166
pop (Stk _ s) = s
newStack = EmptyStk
A utilizao do tipo abstrato Stack deve ser feita em outros mdulos cujas funes neces-
sitam deste tipo de dado. Desta forma, o mdulo Stack deve constar na relao dos mdulos
importados pelo mdulo usurio. Por exemplo,
Este script deve ser salvo no arquivo Main.hs e deve ser carregado para ser utilizado. Agora
podemos construir exemplos de algumas instncias do tipo abstrato Stack, da seguinte forma:
167
newStack :: Stack t
newStack = Stk [ ]
O mdulo Main o mesmo para as duas implementaes, no sendo necessrio ser mostrado
novamente.
Vamos agora mostrar um segundo exemplo de tipo abstrato, o tipo Fila. Uma fila uma estrutura
de dados do tipo FIFO (First-In First-Out), onde os elementos s podem ser inseridos em
um lado e s podem ser retirados do outro (se existir algum elemento na fila), imitando uma fila
de espera. Podemos implementar o tipo Fila atravs de listas finitas, fazendo algumas restries
nas operaes. Para um usurio comum, importante uma implementao eficiente, mas ele no
est interessado como ela feita, normalmente solicitando a outra pessoa que a faa ou utiliza
uma soluo provida pelo sistema.
Da mesma forma feita para o tipo abstrato Pilha, precisamos conhecer que operaes prim-
itivas so necessrias para compor a assinatura do tipo abstrato Queue t. Neste caso, so
elas:
Vamos continuar o exemplo provendo duas implementaes do tipo abstrato Fila, da mesma
forma feita para o tipo Pilha.
A primeira implementao a ser mostrada baseada em uma lista finita. Neste caso, os valores
so colocados no final da lista e so retirados da cabea da lista. A implementao do mdulo
Queue com suas operaes feita da seguinte forma:
168
module Queue (Queue, enqueue, dequeue, front, queueEmpty, newQueue) where
enqueue :: t -> Queue t -> Queue t
dequeue :: Queue t -> Queue t
front :: Queue t -> t
queueEmpty :: Queue t -> Bool
newQueue :: Queue t
newQueue = (Fila [ ])
Para utilizar o TAD Fila necessrio construir o mdulo Main, de forma similar a que foi
feita para a utilizao do TAD Pilha.
169
q1 = enqueue 14 (enqueue 9 (enqueue 19 newQueue))
A segunda implementao do TAD Fila tambm baseada em listas finitas, mas leva em con-
siderao o desempenho da implementao. Para isto, nada necessrio modificar na assinatura
do TAD, no entanto, as implementaes de algumas funes devem ser modificadas para refletir
a nova estrutura.
Recordemos que, na implementao anterior, a insero de dados era feita na cauda da lista,
enquanto a remoo de um dado era feita na cabea da lista. Desta forma, a operao de remoo
era uma operao muito barata" por envolver apenas um acesso cabea da lista, enquanto a
operao de insero poderia ser cara" porque era necessrio percorrer toda a lista para inserir
um valor e a lista pode ser grande. A complexidade desta operao diretamente proporcional
ao tamanho da lista a ser percorrida. No entanto, deve-se observar que esta estratgia pode ser
invertida, ou seja, podemos inserir os dados na cabea da lista e retir-los da cauda. Neste caso,
os desempenhos das duas operaes se inverteriam: a operao de insero, que era cara, ficaria
barata, enquanto a operao de remoo, que era barata, ficaria cara. Isto significa que a
adoo desta nova estratgia em nada adiantaria, em termos de desempenho.
No entanto, podemos juntar estas duas tcnicas em uma s, ou seja, podemos implementar
o tipo Fila usando duas listas: a primeira para fazermos remoo na cabea e a segunda para
fazermos insero, tambm na cabea. Se a primeira lista se tornar vazia, a segunda lista ser
invertida e toma o lugar da primeira lista e a segunda lista passa a ser a lista vazia. Desta forma
a operao de remoo pode continuar a ser feita sem qualquer problema, at que as duas listas
sejam ambas vazias. A nova implementao fica da seguinte forma:
newQueue = Fila [ ] [ ]
170
lista nica. O mdulo de utilizao pode ser o mesmo anterior, sem qualquer alterao, uma vez
que a implementao totalmente transparente para os usurios.
O tipo abstrato Set uma coleo homognea de elementos e implementa a noo de conjunto,
de acordo com a seguinte Interface:
emptySet :: Set t
setEmpty :: Set t -> Bool
inSet :: (Eq t) => t -> Set t -> Bool
addSet :: (Eq t) => t -> Set t -> Set t
delSet :: (Eq t) => t -> Set t -> Set t
pickSet :: Set t -> t
emptySet = S [ ]
setEmpty (S [ ]) = True
setEmpty _ = False
inSet _ (S [ ]) = False
inSet x (S (y : ys)) |x == y = True
|otherwise = inSet x (S ys)
addSet x (S s) |(elem x s) = S s
|otherwise = S (x : s)
delSet x (S s) = S (delete x s)
delete x [ ] = [ ]
delete x (y : ys) |x == y = delete x ys
|otherwise = y : (delete x ys)
171
pickSet (S [ ]) = error "conjunto vazio"
pickSet (S (x : _)) = x
Uma tabela, Table a b, uma coleo de associaes entre chaves, do tipo a, com valores do
tipo b, implementando assim, uma funo finita, com domnio em a e co-domnio b, atravs de
uma determinada estrutura de dados.
O tipo abstrato Table pode ter a seguinte implementao:
newTable = Tab [ ]
Como pode ser observado, o TAD tabela foi implementado usando uma lista de pares (chave,
valor) ordenada em ordem crescente pelas chaves. O mdulo Main para este TAD pode ser
implementado da seguinte forma:
import Table
172
type Numero = Integer
type Nome = String
type Data = Integer
Neste caso, podemos usar a funo pauta para transformar a lista teste em uma tabela em
que a chave seja o Nmero de uma pessoa e o valor correspondente a este nmero seja a dupla
(Nome, Data). Nesta situao, podemos usar as funes definidas em Table para manipular a
lista de triplas.
6.7 Arrays
At este ponto, ainda no nos referimos implementao de arrays em Haskell. Estes tipos de
dados so muito teis na resoluo de problemas e merecem ser analisados como eles podem ser
utilizados nesta linguagem.
Os arrays, baseado em [34, 36], so usados para armazenar e recuperar um conjunto de
elementos, onde cada um deles tem um nico ndice, indicando a sua posio dentro do arranjo.
Nesta seo, ser descrito como estas estruturas podem ser utilizadas em Haskell. Na realidade,
o padro ainda em vigor para Haskell no contemplou estas estruturas e, por este motivo, elas
no fazem parte do Prelude padro. No entanto, foi criado um mdulo com a implementao
destas estruturas como um tipo abstrato de dados e pode ser importado.
onde o primeiro argumento, <limites>, descreve os limites inferior e superior dos ndices do
array, entre parnteses. Por exemplo, (0,10), para arrays unidimensionais, ou ((1,1),(5,5))
para arrays bidimensionais, enfatizando que os valores dos ndices tambm podem ser expresses.
O segundo argumento, <lista_de_associacoes>, uma lista da forma (i,v), sendo i o ndice
do array e v o valor da associao. Normalmente, a lista das associaes so criadas atravs de
expresses ZF. Como exemplo de alguns arrays, podemos citar:
173
x :: Array Int Char
fun :: Int -> Array Int Int
y :: Array (Int, Int) Int
Um array se torna indefinido se qualquer ndice for especificado fora de seus limites. Tambm,
se duas associaes tiverem o mesmo ndice far com que o valor do ndice seja indefinido. Isto
significa que um array estrito em seus limites e lazy em seus valores. Como exemplo, a funo
fib pode ser definida usando a funo array, da seguinte forma:
fib n = a
where a = array (1,n) ([(1,1),(2,1)] ++ [(i,a!(i-1)+a!(i-2))|i<-[3..n]])
onde o operador ! uma funo binria e infixa, de forma que a!i retorna o valor do elemento
de ndice i do array a.
A segunda funo pr-definida sobre arrays listArray, cuja sintaxe a seguinte:
onde o argumento <limites> tem o mesmo significado j visto para a funo array e o ar-
gumento <lista_de_valores> a lista dos valores do array. Para exemplificar, o array x,
definido anteriormente, tambm pode ser construdo usando a funo listArray, da seguinte
forma:
Esta funo remove a restrio de que um dado ndice s possa aparecer, no mximo, uma
vez na lista de associaes, combinando os ndices conflitantes atravs da funo argumento
<funcao> e inicializando os valores do array com <init>. Os outros argumentos so os mesmos
j conhecidos para as duas funes anteriores. Por exemplo, o script a seguir mostra uma
chamada e o resultado utilizando esta funo:
em que todos os elementos do array so inicializados com o valor 2. Como no array existem dois
valores para o ndice 1, a funo (+) resolve esta indefinio somando os valores 2 e 3 ao valor
inicializado, totalizando 7. Os ndices 3 e 5 no constam na chamada, mas so inicializados com
o valor 2 que o segundo parmetro da funo.
J foi visto que a funo ! retorna o valor do elemento mostrado cujo ndice o segundo
argumento. Outras funes tambm j existem para manipular arrays. So elas: bounds -
174
exibe os limites de um array, indices - retorna os ndices do array, elems - retorna os elementos
do array e assocs que retorna as associaes do array. Sendo y o array definido na sub-seo
anterior, ento:
y!(1,2) => 2
bounds y => ((1,1),(2,2))
indices y => [(1,1),(1,2),(2,1),(2,2)]
elems y => [1,2,2,4]
assocs y => [((1,1),1),((1,2),2),((2,1),2),((2,2),4)]
6.8 Resumo
Este Captulo foi reservado ao estudo dos mdulos e como eles podem ser construdos em Haskell.
Isto foi necessrio para que o usurio possa utilizar este recurso que torna possvel a programao
de grandes sistemas, de forma otimizada e gerencivel. A modularidade permite que os mdulos
sejam r-compilados e testados de forma independente, facilitando o gerenciamento na construo
de grandes programas.
Alm da modularidade, no Captulo tambm foram tratados os tipos abstratos de dados,
analisando a necessidade de seu surgimento, mostrando porque eles so abstratos em relao aos
tipos de dados vistos at aqui, que tm representaes concretas e acessveis aos usurios.
Foi visto como eles podem ser construdos em Haskell permitindo ao leitor que possa segu-los
e compreenda como outros tipos podem ser implementados, levando em considerao o desem-
penho de sua representao e de seus mtodos. J foi dito neste estudo que programar simular
problemas contruindo modelos para represent-los, de forma que estes modelos possam ser pro-
cessados por um computador, emitindo solues que sejam interpretadas como solues para os
problemas simulados.
As fontes de exemplos e exerccios mostrados neste Captulo, foram os livros de Richard Bird
[4] e de Fethi Rabhi e Guy Lapalme [34].
Para quem deseja conhecer mais aplicaes de programas funcionais, o livro de Paul Hudak
[11] uma excelente fonte de estudo, principalmente para quem deseja conhecer aplicaes da
multimdia usando Haskell.
175
176
Captulo 7
7.1 Introduo
Este Captulo dedicado forma utilizada por Haskell para se comunicar com o mundo exterior
para realizar operaes de I/O que so inerentemente pertencentes ao mundo imperativo e,
portanto, possveis de conter efeitos colaterais. J foi visto anteriormente que, no paradigma
funcional, os programas so expresses a serem avaliadas para se encontrarem valores que so
atribudos a nomes. Em Haskell, o resultado de um programa o valor da avaliao de uma
funo que atribudo ao identificador main no Mdulo Main do arquivo Main.hs.
Mas a realidade que a grande maioria dos programas exige alguma interao com o mundo
externo. Por exemplo:
inputInt :: Int
cujo efeito a leitura de um valor inteiro a partir do dispositivo padro de entrada. Este
valor lido atribudo ao identificador inputInt. Mas surge um problema porque, cada vez
que inputInt avaliado, um novo valor a ele atribudo sendo esta uma caracterstica do
paradigma imperativo, no do modelo funcional. Por este motivo, diz-se que SML admite um
modelo funcional impuro, porque admite atribuies destrutivas.
Seja a seguinte definio de uma funo, em SML, que calcula a diferena entre dois inteiros:
177
inputDif = inputInt - inputInt
Suponha que o primeiro tem de entrada seja 10 e o segundo seja 20. Dependendo da ordem
em que os argumentos de inputDif so avaliados, ela pode ter como resultado os valores 10 ou
-10. Isto corrompe o modelo, uma vez que era esperado que o valor fosse 0 (zero) para inputDif.
A razo deste problema que o significado de uma expresso no determinado simplesmente
pela observao dos significados de suas partes, porque no se pode atribuir um significado
a inputInt sem antes saber em que local do programa ele ocorre. A primeira e a segunda
ocorrncias de inputInt em inputDif podem acontecer em diferentes tempos e podem ter
valores diferentes. Este tema foi analisado exaustivamente no Captulo 1 deste trabalho.
Um segundo problema com estas operaes de I/O e que os programas se tornam extrema-
mente difceis de serem seguidos, porque qualquer definio em um programa pode ser afetada
pela presena de operaes de entrada e sada.
Por causa disto, durante muito tempo, as operaes de I/O se tornaram um desafio para as
linguagens funcionais e vrias tentativas foram feitas na busca de solues que no alterassem o
paradigma funcional.
O Comit de definio da linguagem Haskell resolveu manter a linguagem pura, sem side effects,
e, por este motivo, a deciso sobre o sistema de I/O foi um dilema a ser resolvido. Eles no
queriam perder o poder de expressividade da linguagem apenas porque ela tinha que ser pura
e a comunicao com o mundo externo era uma necessidade pragmtica. Havia o receio de que
Haskell passase a ser considerada uma linguagem de brincadeira se este tema fosse considerado
de pouca importncia em sua definio.
Inicialmente, duas solues foram propostas: streams e continuations (continuaes). Estes
dois temas j eram dominados teoricamente e pareciam oferecer as condies de expressividade
exigidas, alm de ambas serem puras. No decorrer dessas discusses, verificou-se que estas tcni-
cas eram funcionalmente equivalentes, ou seja, era possvel modelar streams usando continuaes
e vice-versa. Desta forma, no Haskell 1.0 Report, o sistema de I/O foi definido em termos de
streams, incluindo continuaes [12].
Em 1989, Eugenio Moggi publicou no LICS um artigo sobre o uso de Mnadas, originadas da
Teoria das Categorias [41] para descrever caractersticas das linguagens de programao. Philip
Wadler verificou que a tcnica que Moggi havia utilizado para estruturar semntica tambm
poderia ser empregada com sucesso para estruturar programas funcionais [48, 49].
Uma mnada consiste em um construtor de tipos M e um par de funes: return e >>=,
algumas vezes, esta ltima chamada de bind. Seus tipos so:
return :: a -> M a
(>>=) :: M a -> (a -> M b) -> M b
M a deve ser lido como o tipo de uma computao que retorna um valor do tipo a, com
possveis side effects. Digamos que m seja uma expresso do tipo M a e n seja uma expresso
do tipo M b com uma varivel livre, x, do tipo a. Assim, a expresso
tem o tipo M b. Isto significa que a computao indicada por m feita ligando-se o valor
retornado a x e em seguida realiza-se a computao indicada por n. Isto anlogo expresso
178
let x = m in n
em uma linguagem com side effects, como ML, excluindo o fato de que os tipos no indicam a
presena destes efeitos. Na verso de ML, m tem o tipo a em vez de M a e n tem o tipo b em
vez de M b.
Existem trs leis que as definies de return e >>= devem obedecer para que a estrutura
seja considerada uma mnada, no sentido definido pela teoria das categorias. Estas leis garantem
que a composio de funes com side effects seja associativa e tenha uma identidade [49].
Uma mnada um tipo de padro de programao. Este padro pode ser expresso em
Haskell usando type class da seguinte forma:
Como j descrito, um programa funcional consiste em uma expresso que avaliada para en-
contrar um valor que deve ser retornado e ligado a um identificador. Quando as operaes no
se tratarem de expresses, por exemplo, no caso de uma operao de I/O, que valor deve ser
retornado? Este retorno necessrio para que o paradigma seja respeitado. Caso contrrio, ele
corrompido.
A soluo adotada pelos idealizadores de Haskell foi introduzir um tipo especial chamado
ao". Quando o sistema detecta uma operao deste tipo, ele sabe que uma ao deve ser
executada. Existem aes primitivas, por exemplo escrever um caractere em um arquivo ou
receber um caractere do teclado, mas tambm aes compostas como imprimir vrias strings.
As operaes, em Haskell, cujos resultados de suas avaliaes sejam aes, so chamadas
de comandos", porque elas comandam o sistema para realizar alguma ao. As funes, cu-
jos retornos sejam aes, tambm so chamadas de comandos. Os comandos realizam aes e
retornam um valor de um determinado tipo, que pode ser usado futuramente pelo programa.
Haskell prov o tipo IO a para permitir que um programa faa alguma operao de I/O e
retorne um valor do tipo a. Haskell tambm prov um tipo IO () que contm um nico elemento,
representado por (). Uma funo do tipo IO () representa uma operao de I/O (ao) que
retorna o valor (). Semanticamente, este o mesmo resultado de uma operao de I/O que no
retorna qualquer valor. Por exemplo, a operao de escrever a string Olha eu aqui!" pode ser
entendida desta forma, ou seja, um objeto do tipo IO ().
Existem muitas funes pr-definidas em Haskell para realizar aes, alm de um mecanismo
179
para seqencializ-las, permitindo que alguma ao do modelo imperativo seja realizada sem
ferir o modelo funcional.
getChar :: IO Char
Para ler uma string, a partir do dispositivo padro de entrada, usamos a funo pr-definida
getLine do tipo:
getLine :: IO String
As aplicaes destas funes devem ser interpretadas como operaes de leitura seguidas de
retornos; no primeiro caso de um caractere e, no segundo, de uma string.
A operao de impresso de um texto, feita por uma funo que toma a string a ser escrita
como entrada, escreve esta string no dispositivo padro de sada e retorna um valor do tipo ().
Esta funo foi citada no Captulo 3, mas de forma genrica e sem nenhuma profundidade, uma
vez que, seria difcil o leitor entender sua utilizao com os conhecimentos sobre Haskell, at
aquele ponto, adquiridos. Esta funo putStr, pr-definida em Haskell, com o seguinte tipo:
aloGalvao :: IO ()
aloGalvao = putStr "Olha eu aqui!"
Usando putStr podemos definir uma funo que escreva uma linha de sada:
cujo efeito adicionar o caractere de nova linha ao fim da entrada passada para putStr.
Para escrever valores em geral, Haskell prov a classe Show com a funo
que usada para transformar valores, de vrios tipos, em strings para que possam ser mostradas
atravs da funo putStr. Por exemplo, pode-se definir uma funo de impresso geral
Se o objetivo for definir uma ao de I/O que no realize qualquer operao de I/O, mas que
retorne um valor, pode-se utilizar a funo
return :: a -> IO a
180
7.2.3 O comando do
O comando do um mecanismo flexvel, construdo para realizar duas operaes sobre aes em
Haskell:
2. a captura de valores retornados por aes de I/O, para repass-los futuramente para outras
aes do programa.
Por exemplo, a funo putStrLn str, descrita anteriormente, pr-definida em Haskell e faz
parte do Prelude padro. Ela realiza duas aes. a primeira escrever a string str no dispositivo
padro de sada e a segunda fazer com que o prompt salte para a prxima linha. Esta operao
pode ser definida utilizando-se a notao do, da seguinte forma:
Neste caso, o efeito do comando do a seqencializao das aes de I/O, em uma nica
ao. A sintaxe do comando do regida pela regra do offside e pode-se tomar qualquer nmero
de argumentos (aes).
Como outro exemplo, pode-se querer escrever alguma coisa n vezes. Por exemplo, pode-se
querer executar 4 vezes a mesma operao do exemplo anterior. Uma primeira verso pode ser
o seguinte cdigo em Haskell:
Apesar de funcionar corretamente, esta declarao mais parece com o mtodo da fora
bruta". Uma forma mais elegante de descrev-la transformar a quantidade de vezes que se
deseja que a ao seja executada em um parmetro.
Deve ser observada a recurso na cauda utilizada na definio da funo fazNvezes, sim-
ulando a instruo de controle while, to comum nas linguagens imperativas. Agora a funo
faz4vezes pode ser redefinida por
faz4vezes = fazNvezes 4
Apesar de terem sido mostrados apenas exemplos de sada, as entradas tambm podem ser
parte de um conjunto de aes seqencializadas. Por exemplo, pode-se querer ler duas linhas do
dispositivo de entrada padro e escrever a frase duas linhas lidas", ao final. Isto pode ser feito
da seguinte forma:
181
leia2linhas :: IO ()
leia2linhas = do getLine
getLine
putStrLn "duas linhas lidas"
No ltimo exemplo mostrado, foram lidas duas linhas mas nada foi feito com o resultado das
aes de getLine. Pode ser necessrio utilizar estes resultados no restante do programa. Isto
feito atravs da nomeao dos resultados das aes. Por exemplo,
getNput :: IO ()
getNput = do linha <- getLine
putStrLn linha
leiaAte :: IO ( )
leiaAte = do nome <- getline
if nome == "Final"
then return ()
else do (n1 <- getDouble
n2 <- getDouble
n3 <- getDouble
putStr (nome ++ show ((n1+n2+n3)/3.0) ++ "\n")
leiaAte)
A ao getDouble recebe um dado como entrada e interpreta o valor deste dado como um
tipo Double. Ela definida da seguinte forma:
getDouble :: IO Double
getDouble = do dado <- getLine
return (read dado :: Double)
Os arquivos so considerados como variveis permanentes, cujos valores podem ser lidos ou
atualizados em momentos futuros, depois que o programa que os criou tenha terminada a sua
execuo. desnecessrio comentar a importncia que estas variveis tm para a computao.
No entanto, necessria uma forma de comunicao do programa com os arquivos. J foi vista
uma forma, atravs do comando do. A outra atravs de descritores.
182
Para obter um descritor (do tipo Handle) de um arquivo necessria a operao de abertura
deste arquivo para que futuras operaes de leitura e/ou escrita possam acontecer. Alm disso,
necessria uma operao de fechamento deste arquivo, aps suas operaes terem sido realizadas,
para que os dados no sejam perdidos quando o programa terminar sua execuo.
A Tabela 7.1 mpstra as formas como os arquivos podem ser abertos, operados e fechados em
Haskell, usando o comando openFile.
Os programas em Haskell podem trabalhar com arquivos tipo texto ou com arquivos binrios.
Neste ltimo caso, necessrio utilizar openBinaryFile em vez de openFile. O sistema opera-
cional Windows processa arquivos textos de forma diferente como processa arquivos binrios. O
sistema operacional Linux utiliza tanto openFile quanto openBinaryFile, mas aconselhvel
usar openBinaryFile para processar arquivos binrios para efeito de portabilidade.
Estas operaes so descritas em Haskell, da seguinte forma:
Por conveno, todos os comandos usados para tratar descritores de arquivos so iniciadas
com a letra h. Por exemplo, as funes
No entanto, h que se fazer uma observao. Apesar de parecer que hGetContents retorna
todo o contedo de um arquivo de uma nica vez, no realmente isto o que acontece. Na
realidade, a funo retorna uma lista de caracteres, como esperado, mas de forma lazy, onde os
elementos so lidos sob demanda.
183
7.3.1 A necessidade dos descritores
Lembremos que um arquivo tambm pode ser escrito sem o uso de descritores. Por exemplo,
pode-se escrever em um arquivo usando o comando
import System.IO
import Data.Char(toUpper)
main :: IO ()
main = do entrada <- openFile "entra.txt" ReadMode
saida <- openFile "sai.txt" WriteMode
loop_principal entrada saida
hClose entrada
hClose saida
184
loop_principal entrada saida =
do fim_de_arquivo <- hIsEOF entrada
if fim_de_arquivo then return ()
else do inpStr <- hGetLine entrada
hPutStrLn saida (map toUpper inpStr)
loop_principal entrada saida
A execuo deste programa, como em qualquer outro em Haskell, se inicia com a funo
main. Neste caso, o arquivo "entra.txt" aberto no modo de leitura e o arquivo "sai.txt"
aberto no modo de escrita. Em seguida o programa chama a funo loop_principal que faz
a leitura de uma linha do arquivo "entra.txt". Essa linha lida tem todos os seus caracteres
trocados para caracteres maisculos e escrita no arquivo "sai.txt". A funo loop_principal
chamada recursivamente para um novo ciclo de leitura e processamento de mais uma linha,
equivalendo a um loop em uma linguagem imperativa. Este ciclo repetido at que o arquivo
"entra.txt" atinja seu final. Finalmente os arquivos "entra.txt" e "sai.txt" so fechados.
Deve ser observado o papel da funo return () em programas em Haskell que diferente
dos propsitos nas linguagens imperativas. Por exemplo, em C, esta funo tem o propsito de
encerrar a execuo da funo corrente. Em Haskell, a funo return () tem significado oposto
ao da funo ->, ou seja, toma um valor puro e o coloca dentro de uma ao de IO. Por exemplo,
sendo 10 um valor inteiro do tipo Integer, ento return 10 cria uma ao armazenada em um
valor do tipo IO Integer. Quando esta ao for executada, ela produz o valor 10.
Este mesmo programa pode ser feito de outra forma, usando hGetContents que uma
forma de leitura usando o mecanismo de leitura lasy empregado em Haskell.
import System.IO
import Data.Char(toUpper)
main :: IO ()
main = do entrada <- openFile "entra.txt" ReadMode
saida <- openFile "sai.txt" WriteMode
inpStr <- hGetContents entrada
let resultado = processaDado inpStr
hPutStr saida resultado
hClose entrada
hClose saida
Este programa ainda pode ser modificado, transformando-o em outro ainda mais compacto.
import System.IO
import Data.Char(toUpper)
main :: IO ()
main = do entrada <- openFile "entra.txt" ReadMode
saida <- openFile "sai.txt" WriteMode
inpStr <- hGetContents entrada
hPutStr saida (map toUpper inpStr)
hClose entrada
hClose saida
185
A partir destes exemplos, pode-se observar que o comando hGetContents utilizado como
um filtro, onde os dados so lidos em um arquivo, processados e escritos em um outro arquivo.
Este tipo de processamento bastante comum e, por este motivo, foram criadas duas funes para
atender a este demanda: readFile e writeFile. Estas duas funes gerenciam todos os detalhes
de abertura, leitura, processamento e fechamento de arquivos como strings. A funo readFile
usa hGetContents internamente. Desta forma, o exemplo anterior pode ser construdo ainda
mais sinteticamente.
import Data.Char(toUpper)
main :: IO ()
main = do inpStr <- readFile "entra.txt"
writeFile "saida.txt" (map toUpper inpStr)
7.3.2 Canais
Os descritores tambm podem ser associados a canais, que so portas de comunicao no asso-
ciadas diretamente a um arquivo. Os canais mais comuns so: a entrada padro (stdin), a rea
de sada padro (stdout) e a rea de erro padro (stderr). As operaes de I/O para caracteres
e strings em canais incluem as mesmas listadas anteriormente para a manipulao de arquivos.
Na realidade, as funes getChar e putChar so definidas como:
At mesmo hGetContents pode ser usada com canais. Neste caso, o fim de um canal
sinalizado com um cartactere de fim de canal que, na maioria dos sistemas, Ctrl-d.
Normalmente, um descritor um arquivo, mas pode ser tambm uma conexo de rede, um
drive ou um terminal. Para verificar se um dado descritor pode, ou no, ser varrido, pode-se
usar o comando hIsSeekable que retorna um valor booleano.
Vamos agora nos reportar a erros que podem acontecer durante as operaes de I/O. Por exemplo,
pode-se tentar abrir um aquivo que ainda no existe, ou pode-se tentar ler um caractere de um
arquivo que j atingiu seu final. Certamente, no se deve querer que o programa pre por
estes motivos. O que normalmente se espera, que o erro seja reportado como uma condio
anmala, mas que possa ser corrigida, sem a necessidade de que o programa seja abortado. Para
fazer este gerenciamento de excees, em Haskell, so necessrios apenas alguns comandos de
IO.
As excees tm o tipo IOError. Entre as operaes permitidas sobre este tipo, est uma
coleo de predicados que podem ser usados para testar tipos particulares de excees. Por
exemplo,
186
catch :: IO a -> (IOError -> IO a) -> IO a
No comando catch com ger, a ao que ocorre em com (pode at gerar uma seqncia
longa de aes, podendo at mesmo ser infinita) ser executada pelo gerenciador ger. O controle
efetivamente transferido para o gerenciador atravs de sua aplicao exceo IOError. Por
exemplo, a verso de getChar, a seguir, retorna um caractere de uma nova linha, se algum tipo
de execuo for encontrada.
getChar :: IO Char
getChar = catch getChar (\e -> return \n)
No entanto, esta verso trata todas as excees da mesma maneira. Se apenas a exceo de
fim de arquivo deve ser reconhecida, o valor de IOError deve ser solicitado.
getChar :: IO Char
getChar = catch getChar (\e -> if isEOFError e then return \n
else ioError e)
A funo isError, usada neste exemplo, empurra" a exceo para o prximo gerenciador de
excees. Em outras palavras, permitem-se chamadas aninhadas a catch e estas, por sua vez,
produzem gerenciadores de excees, tambm aninhados. A funo ioError pode ser chamada
de dentro de uma seqncia de aes normais ou a partir de um gerenciador de excees, como
em getChar, deste exemplo.
Usando-se getChar, pode-se redefinir getLine para demonstrar o uso de gerenciadores
aninhados.
getLine :: IO String
getLine = catch getLine (\err -> "Error: " ++ show err)
where getLine = do c <- getChar
if c == \n then return ""
else do l <- getLine
return (c:l)
Exemplo final. O exemplo a seguir foi retirado do livro de Cludio Csar S [36].
--Funo principal --
{--Antes do menu de opes ser exibido, deve ser realizada uma checagem
a fim dd verificar se o arquivo vazio, ou no. Se o arquivo estiver vazio,
necessrio que a incluso de uma lista vazia ([]) seja realizada para que as
demais operaes possam ser realizadas com sucesso.
--}
--
-- Menu de opes --
187
menu :: IO ( )
menu = do putStrLn " "
putStrLn " ---------------------------------------"
putStrLn "| |"
putStrLn "| CADASTRO DE PESSOAS |"
putStrLn "| |"
putStrLn "| a - Insere cadastro |"
putStrLn "| b - Imprime cadastro |"
putStrLn "| c - Busca por nomes |"
putStrLn "| d - Soma das idades |"
putStrLn "| e - Mdia das alturas |"
putStrLn "| f - Busca por sexo |"
putStrLn "| g - Excluir um cadastro |"
putStrLn "| h - Excluir todos os cadastros |"
putStrLn "| i - Sair do sistema |"
putStrLn "|_______________________________________|"
putStr "Digite uma das opes: "
le_opcao
le_opcao :: IO ( )
le_opcao = do opcao <- getChar
putStr "\n"
f_menu (toUpper opcao)
insere_cadastro :: IO ( )
insere_cadastro =
do putStrLn "Nome: "
nm <- getLine
putStrLn "Idade: "
id <- getLine
putStrLn "Altura: "
alt <- getLine
putStrLn "M - Masculino | F - Feminino"
putStrLn "Sexo: "
sex <- getChar
let cadastro = nm++"#"++id++"#"++alt++"#"++[(toUpper sex)]++""
pt_arq <- abreArquivo "dados.txt" AppendMode
188
hPutStrLn pt_arq cadastro
fechaArquivo pt_arq
imprime_cadastros :: IO ( )
imprime_cadastros =
do putStrLn " "
putStrLn "---------------------------------------------------------"
pt_arq <- abreArquivo "dados.txt" ReadMode
conteudo <- (hGetContents pt_arq)
cadastros <- (converteConteudo (conteudo))
imprime cadastros
fechaArquivo pt_arq
putStrLn "--------------------------------------------------------"
busca_p_nomes :: IO ( )
busca_p_nomes =
do putStrLn " "
putStrLn "digite o nome desejado: "
nome <- getLine
busca_p_algo busca_por_nome nome
soma_d_idades :: IO ( )
soma_d_idades =
do pt_arq <- abreArquivo "dados.txt" ReadMode
conteudo <- (hGetContents pt_arq)
cadastros <- (converteConteudo (conteudo))
putStrLn (show (soma_d_idade cadastros))
fechaArquivo pt_arq
media_d_alturas :: IO ( )
media_d_alturas =
do pt_arq <- abreArquivo "dados.txt" ReadMode
conteudo <- (hGetContents pt_arq)
cadastros <- (converteConteudo (conteudo))
putStrLn (show (media_d_altura cadastros))
fechaArquivo pt_arq
busca_p_sexo :: IO ( )
busca_p_sexo =
do putStrLn " "
putStrLn "digite o sexo desejado: "
sexo <- getChar
busca_p_algo busca_por_sexo sexo
excluir_um_cadastro :: IO ( )
excluir_um_cadastro =
do putStrLn "O cadastro ser apagado pelo nome."
putStrLn "Digite o nome desejado: "
nome <- getLine
pt_arq <- abreArquivo "dados.txt" ReadMode
conteudo <- (hGetContents pt_arq)
189
cadastros <- (converteConteudo (conteudo))
let novo_conteudo = apaga_p_nome cadastros nome
aux_pt_arq <- abreArquivo "auxiliar.txt" WriteMode
hPutStr aux_pt_arq novo_conteudo
fechaArquivo aux_pt_arq
fechaArquivo pt_arq
copiar "auxiliar.txt" "dados.txt"
excluir_todos_cadastros :: IO ( )
excluir_todos_cadastros =
do putStrLn "Tem certeza que deseja apagar todos os dados do sistema?(s/n)"
resp <- getChar
if not ((toUpper resp) == S) then putStrLn " "
else do pt_arq <- abreArquivo "dados.txt" WriteMode
fechaArquivo pt_arq
putStrLn "Apagando dados . . ."
190
nome (a:b:c:d:[ ]) = a
idade (a:b:c:d:[ ]) = b
altura (a:b:c:d:[ ]) = c
sexo (a:b:c:d:[ ]) = d
--FUNO DE INCLUSO
191
apaga_p_nome [ ] nm = "\n"
apaga_p_nome (x : xs) nm
|nm == (nome x) = (apaga_p_nome xs nm)
|otherwise = (foldl1 (\a b->a++"#"++b) x) ++ "\n" ++ (apaga_p_nome xs nm)
7.5 Resumo
Este foi o Captulo final deste trabalho, dedicado semntica de aes, adotadas em Haskell,
para tratar operaes de entrada e sada, representando a comunicao que o programa deve
ter com perifricos e com os arquivos. Na realidade, ela implementada em Haskell atravs de
Mnadas, uma teoria matemtica bastante complexa e que, por este motivo, est fora do escopo
deste estudo.
O objetivo do Captulo foi mostrar as formas como Haskell trata as entradas e as sadas de
dados, ou seja, que facilidades a linguagem Haskell oferece para a comunicao com o mundo
externo ao programa. Isto inclui a leitura e escrita de dados em arquivos, bem como a criao e
o fechamento de arquivos, armazenando dados para serem utilizados futuramente.
Este estudo foi baseado nos livros de Simon Thompson [46], de Richard Bird [4] e de Paul
Hudak [11], como tambm nos excelentes textos de Philip Wadler [48, 49]. Este tema ainda
considerado novo e complexo pelos pesquisadores das linguagens funcionais e acreditamos ser o
motivo pelo qual ele ainda muito pouco tratado na literatura.
Com este Captulo, esperamos ter cumprido nosso objetivo que foi o de proporcionar aos
estudantes iniciantes das linguagens funcionais, alguma fundamentao e conceitos alm de de-
senvolver algumas aplicaes utilizando a linguagem de programao Haskell.
192
Bibliografia
[1] AHO, Alfred V; SETHI, Ravi et ULLMAN, Jeffrey D. Compilers, Principles, Techniques,
and Tools. 2nd. Edition. Addison-Wesley Publishing Company; 1988.
[2] ANDRADE, Carlos Anreazza Rego. AspectH: Uma Extenso Orientada a Aspectos de
Haskell. Dissertao de Mestrado. Centro de Informtica. UFPE. Recife, Fevereiro 2005.
[3] BARENDREGT, H. P. The Lambda Calculus: Its Syntax and Semantics. (Revised Edn.).
North Holland, 1984.
[4] BIRD, Richard. Introduction to Functional Programming Using Haskell. 2nd. Edition.
Prentice Hall Series in Computer Science - Series Editors C. A. Hoare and Richard Bird.
1998.
[5] BOYER, Carl B. Histria da Matemtica. Traduo de Elza Gomide. Editora Edgard
Blcher Ltda. So Paulo, 1974.
[7] CURRY, H. B. et FEYS, R. and CRAIG, W. Combinatory Logic. Volume I; North Holland,
1958.
[9] DAVIS, Philip J. et HERSH, Reuben. A Experincia Matemtica. Traduo de Joo Bosco
Pitombeira. Editora Francisco Alves. Rio de Janeiro, 1985.
[10] HINDLAY, J. Roger. Basic Simple Type Theory. Cambridge Tracts in Theorical Computer
Science, 42. Cambridge University Press. 1997.
[11] HUDAK, Paul. The Haskell School of Expression: Learning Funciotonal Programming
Through Multimedia. Cambridge University Press, 2000.
[12] HUDAK, Paul et all. A History of Haskell: being lazy with class. January 2007.
[13] HUGHES, John. Why Functional Programming Matters. In Turner D. T. Ed. Research
Topics in Funcitonal Programming. Addison-Wesley, 1990.
193
[17] JOHNSSON, T. Lambda Lifting: Transforming Programs to Recursive Equations. Aspens
Workshop on Implementation of Functional Languages. Gteborg, 1985.
[18] JOHNSSON, T. Target Code Generation from G-Machine Code. Proc. Workshop on
Graph Reduction, Santa F Lecture Notes on Computer science, Vol: 279 pp. 119-159.
Spring-Verlag, 1986.
[19] JOHNSSON, T. Compiling Lazy Functional Languages. Ph.D. Thesis. Chalmers University
of Technology, 1987.
[22] LINS, Rafael Dueire et LIRA, Bruno O. CMC: A Novel Way of Compiling Functional
Languages. J. Programming Languages 1:19-40; Chapmann & Hall. 1993.
[23] LINS, Rafael Dueire. O -Clculo, Computabilidade & Lingugens de Programao. Notas
de Curso. Recife-Pe, Nov. 1993.
[24] LINS, Rafael Dueire et all. Research Interests in Functional Programming. I Workshop on
Formal Methods. UFRGS. Outubro, 1998.
[27] NIKHIL, R. S. and ARVIND, A. Implicit Parallel Programming in pH. Morgan Kaufman.
2001.
[28] OKASAKI, Chris. Purely Functional Data Structures. Cambridge University Press. 2003.
[29] OSULLIVAN, Bryan et GOERZEN, John et STEWART, Don. Real World Haskell.
OReilly. 2008.
[30] PAULSON, Laurence C. ML for the Working Programmer. Cambridge University Press,
1991.
[33] PEYTON JONES, S. L.; GORDON, A. and FINNE, S. Concurrent Haskell. In 23rd
ACM Symposium on Principles of Programming Languages (POPL96), pages 295-308. St
Petersburg Beach, Florida. ACMPress. 1996.
[35] ROJEMO, Niklaus. Garbage Collection and Memory Efficiency in Lazy Functional Lan-
guages. Ph.D Thesis. Department of Computing Science, Chalmers University. 1995.
194
[36] S, Claudio Cesar. Haskell: uma abordagem prtica. Novatec Editora Ltda. So Paulo,
2006.
[37] SCHMIDT, D. A. Denotational Semantics. Allyn and Bacon, Inc. Massachusetts, 1986.
[38] SCOTT, D. Data Types as Lattices. SIAM Journal of Computing. Vol. 5,3. 1976.
[41] SOUZA, Francisco Vieira de. Teoria das Categorias: A Linguagem da Computao. Exame
de Qualificao. Centro de Informtica. UFPE. 1996.
[42] SOUZA, Francisco Vieira de et LINS, Rafael Dueire. Aspectos do Comportamento Espao-
temporal de Programas Funcionais em Uni e Multiprocessadores. X Simpsio Brasileiro de
Arquitetura de Computadores e Processamento de Alto Desempenho. Bzios-RJ. Setembro.
1998.
[43] SOUZA, Francisco Vieira de et LINS, Rafael Dueire. Analysing Space Behaviour of Func-
tional Programs. Conferncia Latino-americana de Programao Funcional. Recife-Pe.
Maro. 1999.
[44] SOUZA, Francisco Vieira de. Aspectos de Eficincia em Algoritmos para o Gerenciamento
Automtico Dinmico de Memria. Tese de Doutorado. Centro de Informtica-UFPE.
Recife. Novembro. 2000.
[46] THOMPSON, Simon. Haskell: The Craft of Functional Programming. 2nd. Edition. Ad-
dison Wesley. 1999.
[47] TURNER, David A. A New Implementation Technique for Applicative Languages. Software
Practice and Experience. Vol. 9. 1979.
[49] WADLER, Philip. The Essence of Functional Programming. In 20th ACM Symposium on
Principles of Programming Languages (POPL92), pages 1-14. ACM. Albuquerque. 1992.
[50] WADLER, Philip. Why no ones Uses Functional Languages. Functional Programming.
ACM SIGPLAN. 2004.
[51] WELCH, Peter H. The -Calculus. Course notes, The University of Kent at Canterbury,
1982.
195
196
Apndice A
3. (&&). Conjuno:
4. (||). Disjuno:
197
6. and. Retorna a conjuno lgica de uma lista de booleanos:
12. dropWhile. Remove o segmento inicial de uma lista se os elementos do segmento satis-
fizerem uma determinada propriedade:
13. filter. Seleciona os elementos de uma lista que apresentam uma determinada propriedade:
198
15. foldl. Aplica uma funo entre os elementos de uma lista pela esquerda:
16. foldl1. Aplica uma funo entre elementos de listas no vazias, pela esquerda:
17. foldr. Aplica uma funo entre os elementos de uma lista pela direita:
18. foldr1. Aplica uma funo entre elementos de listas no vazias, pela direita:
id :: t -> t
id x = x
23. iterate. Produz uma lista infinita de aplicaes iteradas de uma funo:
199
iterate :: (t -> t) -> t -> [t]
iterate f x = x : iterate f (f x)
200
32. reverse. Inverte os elementos de uma lista finita:
33. scanl. Aplica foldl a um segmento inicial no vazio de uma lista no vazia:
34. scanl1. Aplica foldl1 a todo o segmento inicial no vazio de uma lista no vazia:
201
40. snd. Seleciona o segundo componente de um par:
43. takeWhile. Seleciona o segmento inicial dos elementos de uma lista que satisfazem um
dado predicado:
45. until. Aplicada a um predicado, uma funo e um valor, retorna o resultado da aplicao
da funo ao valor, o menor nmero de vezes para satisfazer o predicado:
202
zip :: [t] -> [u] -> [(t, u)]
zip [ ] ys = [ ]
zip (x : xs) [ ] = [ ]
zip (x : xs) (y : ys) = (x, y) : zip xs ys
203
204
Apndice B
B.1 Introduo
Este Apndice se fez necessrio para mostrar ao usurio como ele pode construir programas
em Haskell e compil-los utilizando GHC (Glasgow Haskell Compiler ). Os programas exe-
cutveis permitem a utilizao de ferramentas importantes na depurao e otimizao de pro-
gramas. Por exemplo, a execuo de um programa pode ser feita com o auxlio do RTS (Run
Time System) que permite que o usurio escolha o coletor de lixo a ser utilizado na execuo
de seu programa, podendo ser o coletor de cpia ou o coletor geracional, o tamanho do heap
de execuo, alm de uma srie de alternativas de profile. Se o usurio assim o desejar, GHC
pode reportar a quantidade de chamadas feitas ao coletor de lixo, mostrando todas as clulas
recicladas e os tempos de CPU, de sistema e total, gastos em cada chamada. Outra ferramenta
de profile importante disponvel em GHC se refere criao de estatsticas sobre as chamadas
a cada funo e os tempos de durao delas que podem ser mostradas de forma grfica, podendo
serem teis na otimizao de cada uma destas funes. Outra informao importante se refere a
quantidade cache miss e pagefaults.
O compilador GHC pode ser instalado em vrias plataformas mas, neste Apndice, ser anal-
isada a sua instalao sob o sistema operacional Windows. Os pacotes binrios de GHC devem
funcionar sob Windows Vista e XP, mesmo Windows 2000.
O primeiro passo para quem deseja instalar GHC visitar a pgina http://www.haskell.org/
e seguir as instrues ali descritas para download e instalao. O instalador tem o nome de ghc-
6.10.3-i386-windows.exe (50MB) que contempla tambm o suporte para compilao de arquivos
C++. Aps realizar o download, d um clique duplo para iniciar o processo de instalao que
envolve alguns passos descritos pelo instalador. Ao trmino do processo de instalao, o submenu
All Programs deve conter o folder GHC, dentro do qual deve existir um cone que pode ser
usado para executar o ghci.
205
B.3 Compilando em GHC
Para a gerao do um arquivo executvel a partir de um programa com diversos mdulos, deve
existir um mdulo Main que ser parte do arquivo Main.hs e deve conter obrigatoriamente a
funo main(). Para compilar um programa standalone, mesmo assim, necessria a existn-
cia da funo main() da mesma forma. Por exemplo, para compilar um programa simples
primeiro.hs, cujo contedo o seguinte,
deve-se escrever este cdigo e salvar o arquivo com o nome primeiro.hs e fazer o seguinte
comando:
ghc -c primeiro.hs
A diretiva de compilao -c informa ao ghc que ele deve gerar apenas cdigo objeto, e
no gerar o programa executvel ainda. Neste caso, os programas primeiro.o e primeiro.hi
sero criados no diretrio que consta o arquivo primeiro.hs, caso nenhum erro seja detectado
no programa. Para gerar o arquivo executvel, primeiro.exe sob a plataforma Windows ou
primeiro sob o sistema operacional Unix ou seus clones necessrio chamar novamente o ghc
da seguinte forma:
O leitor pode verificar que o programa primeiro.exe (em Windows) se encontra do diretrio
em uso e cham-lo diretamente da linha de comando.
Em algumas aplicaes, pode ser extremamente til receber parmetros da linha de comandos.
Em Haskell, isto possvel se for importado o mdulo System.Environment da biblioteca de
Haskell para o script do programa a ser compilado. Utilizando o mesmo arquivo anterior, pode-se
fazer isso da seguinte forma:
import System.Environment
main :: IO ()
main = do
args <- getArgs
putStr (args!!0)
Ao compilar este script em GHC, o programa ir escrever na tela o que for escrito na linha
de comandos, ou seja, a frase que ser passada como argumento atravs da linha de comandos.
O leitor pode verificar que a sintaxe de Haskell para a passagem de parmetros semelhante a
de C, com os argumentos armazenados em uma lista, em vez de em um array como em C. No
caso em voga, os demais parmetros podem ser acessados apenas mudando o valor do elemento
da lista args.
J foi mencionado que GHC oferece uma diversidade de diretivas de compilao, atravs de seus
flags. Algumas diretivas so colocadas para facilitar a otimizao do programa e outras so
206
adicionadas chamada do programa durante a execuo, atravs de chamadas a rotinas do RTS
- Run Time System.
A estrutura de um comando em ghc ghc <comandos> <arquivo>, onde <comandos>
so diretivas (flags) com seus respectivos argumentos, se houver, e <arquivo> corresponde ao
caminho e arquivo contendo o script em Haskell.
De maneira geral, o processo de compilao em Haskell pode gerar vrios arquivos, depen-
dendo das diretivas utilizadas. Entre essas diretivas, algumas podem ser citadas:
Maiores detalhes sobre as diretivas de compilao devem ser pesquisadas no Manual de GHC
que pode ser conseguido na pgina http://www.haskell.org/ghc/documentation.html.
207