Você está na página 1de 216

PROGRAMAO FUNCIONAL

USANDO HASKELL

Programao Funcional
Usando Haskell

Francisco Vieira de Souza


Licenciado em Matemtica (1978) e Engenheiro Civil (1982) pela Universidade Federal
do Piau, Mestre (1994) e Doutor (2000) em Cincia da Computao pela Universidade
Federal de Pernambuco.
Professor do Departamento de Matemtica (1986-1988), fundador (1987) e professor (desde
1987) do Departamento de Informtica e Estatstica da Universidade Federal do Piau.

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.
A reproduo do todo ou parte deste
trabalho somente ser permitida para fins
educacionais e de pesquisa e com a expressa
autorizao do autor.

c
Copyright 2009,

Departamento de Informtica e Estatstica,


Centro de Cincias da Natureza,
Universidade Federal do Piau.
All rights reserved.
Reproduction of all or part of this work only
will be permitted for educational or research use
and with expressed permission of the author.

iii

Apresentao
Esta Apostila representa a compilao de vrios tpicos, desenvolvidos por pesquisadores de
renome, no campo da programao funcional. Ela tem como objetivo servir de guia aos profissionais de Informtica que desejam ter um conhecimento inicial sobre o paradigma funcional de
forma geral e, em particular, sobre programao usando Haskell. Ultimamente, Haskell tem se
tornado a linguagem funcional padro do discurso, j existindo vrios interpretadores e compiladores para ela, alm de vrias ferramentas de anlise de programas nela codificados (profiles).
Para atingir este objetivo, acreditamos que o estudo deva ser acompanhado de algum conhecimento, mesmo que mnimo, sobre a fundamentao destas linguagens e da forma como elas
so implementadas. Este conhecimento proporciona ao leitor uma viso das principais caractersticas e propriedades destas linguagens. Em particular, importante verificar que as tcnicas
utilizadas na compilao das linguagens imperativas no se mostraram adequadas na compilao
de linguagens funcionais porque o cdigo gerado sempre apresentou um desempenho abaixo da
crtica.
Em 1978, John Backus advogou o paradigma funcional como o que oferecia a melhor soluo
para a chamada crise do software. As linguagens funcionais so apenas uma sintaxe mais
cmoda para o -clculo. David Turner [47] mostrou, em 1979, que a lgica combinatorial poderia
ser extendida de forma a possibilitar a implementao eficiente de linguagens funcionais. Esse
trabalho provocou uma corrida em direo pesquisa nesta rea, gerando uma variedade de
tcnicas de implementao destas linguagens.
Dentre estas tcnicas, uma que tem sido adotada, com resultados promissores, a utilizao
do -clculo como linguagem intermediria entre a linguagem de alto nvel e a linguagem de
mquina. Os programas codificados em alguma linguagem funcional de alto nvel so traduzidos
para programas em -clculo e estes so traduzidos para programas em linguagem de mquina.
Neste caso, o -clculo desempenha um papel semelhante ao que a linguagem Assembly exerce,
como linguagem de montagem, na compilao de linguagens imperativas. Esta metodologia tem
dado certo, uma vez que j se conhecem tcnicas eficientes de traduo de programas em -clculo
para programas executveis, faltando apenas uma traduo eficiente de programas codificados em
uma linguagem funcional de alto nvel para programas em -clculo.
Esta Apostila tem incio em seu primeiro Captulo tratando das caractersticas das linguagens funcionais, destacando suas vantagens em relao s linguagens de outros paradigmas que
utilizam atribuies destrutivas. Em seguida, feita uma introduo ao -clculo. Apesar do
carter introdutrio, achamos ser suficiente para quem quer dar os primeiros passos em direo
aprendizagem desta tcnica. Os Captulos subseqentes se referem todos Programao Funcional usando Haskell.
Por ser uma primeira tentativa, a Apostila contm erros e sua apresentao didtico-pedaggica deve ser revista. Neste sentido, agradecemos crticas construtivas que sero objeto de anlise
e reflexo e, por isto mesmo, muito bem-vindas.
Teresina-Pi, setembro de 2009.
Francisco Vieira de Souza
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

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

Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

31

1.5

1.6

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

A semntica operacional do -clculo . . . . . . . . . . . . . . . . . . . . . . . . .

36

2.4.1

Ocorrncias livres ou ligadas . . . . . . . . . . . . . . . . . . . . . . . . . .

38

2.4.2

Combinadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

39

Regras de converso entre -expresses . . . . . . . . . . . . . . . . . . . . . . . .

41

2.4

2.5

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

3 Programao funcional em Haskell


3.1
3.2

3.3

3.4

3.5

Introduo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

53

3.1.1

Outras implementaes . . . . . . . . . . . . . . . . . . . . . . . . . . . .

54

Primeiros passos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

55

3.2.1

O interpretador ghci . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

57

3.2.2

Identificadores em Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . .

58

Funes em Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

59

3.3.1

Construindo funes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

59

3.3.2

Avaliao de funes em Haskell

. . . . . . . . . . . . . . . . . . . . . . .

61

3.3.3

Casamento de padres (patterns matching) . . . . . . . . . . . . . . . . .

62

Tipos de dados em Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

63

3.4.1

Os tipos primitivos da linguagem . . . . . . . . . . . . . . . . . . . . . . .

63

3.4.2

Metodologias de programao . . . . . . . . . . . . . . . . . . . . . . . . .

69

3.4.3

Os tipos de dados estruturados de Haskell . . . . . . . . . . . . . . . . . .

72

3.4.4

Escopo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

73

3.4.5

Expresses condicionais . . . . . . . . . . . . . . . . . . . . . . . . . . . .

74

3.4.6

Clculos:

75

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Projeto de programas

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

77

Provas de programas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

78

Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

81

3.5.1
3.6

53

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.6

4.5.1

A funo map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

98

4.5.2

Funes annimas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100

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.9

4.8.1

Composio avanada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109

4.8.2

Esquema de provas usando composio . . . . . . . . . . . . . . . . . . . . 109

Aplicao parcial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111


4.9.1

Seo de operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112

4.10 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123


5 Tipos algbricos de dados

125

5.1

Introduo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125

5.2

Classes de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125

5.3

5.4

5.5

5.6

5.7

5.2.1

Fundamentao das classes . . . . . . . . . . . . . . . . . . . . . . . . . . 126

5.2.2

Funes que usam igualdade . . . . . . . . . . . . . . . . . . . . . . . . . . 127

5.2.3

Assinaturas e instncias . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128

5.2.4

Classes derivadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128

5.2.5

As classes pr-definidas em Haskell . . . . . . . . . . . . . . . . . . . . . . 130

Tipos algbricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132


5.3.1

Como se define um tipo algbrico? . . . . . . . . . . . . . . . . . . . . . . 133

5.3.2

Tipos recursivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136

5.3.3

Tipos algbricos polimrficos . . . . . . . . . . . . . . . . . . . . . . . . . 137

rvores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
5.4.1

rvores binrias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138

5.4.2

Funes sobre rvores binrias . . . . . . . . . . . . . . . . . . . . . . . . 138

5.4.3

rvores binrias aumentadas . . . . . . . . . . . . . . . . . . . . . . . . . 141

5.4.4

rvores de buscas binrias . . . . . . . . . . . . . . . . . . . . . . . . . . . 142

5.4.5

rvores heap binrias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145

Tratamento de excees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147


5.5.1

Valores fictcios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148

5.5.2

Tipos de erros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149

Lazy evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150


5.6.1

Expresses ZF (revisadas) . . . . . . . . . . . . . . . . . . . . . . . . . . . 153

5.6.2

Dados sob demanda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154

5.6.3

Listas potencialmente infinitas . . . . . . . . . . . . . . . . . . . . . . . . 155

Provas sobre tipos algbricos

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
vii

5.8

Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157

6 Modularizao e Tipos Abstratos de Dados em Haskell

159

6.1

Introduo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159

6.2

Mdulos em Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160

6.3

6.2.1

Controles de exportao . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161

6.2.2

Importao de mdulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161

6.2.3

Controles de importao . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161

Compilao de programas fontes usando GHC . . . . . . . . . . . . . . . . . . . . 162


6.3.1

O mdulo Main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162

6.4

Modularidade e abstrao de dados . . . . . . . . . . . . . . . . . . . . . . . . . . 163

6.5

Eficincia de programas funcionais . . . . . . . . . . . . . . . . . . . . . . . . . . 164


6.5.1

6.6

6.7

6.8

O desempenho da funo reverse . . . . . . . . . . . . . . . . . . . . . . . 164

Tipos abstratos de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165


6.6.1

O tipo abstrato Pilha . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166

6.6.2

O tipo abstrato de dado Fila . . . . . . . . . . . . . . . . . . . . . . . . . 168

6.6.3

O tipo abstrato Set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171

6.6.4

O tipo abstrato Tabela . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172

Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
6.7.1

A criao de arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173

6.7.2

Utilizando arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174

Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175

7 Programao com aes em Haskell


7.1

Introduo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
7.1.1

7.2

7.3

177

Ser ou no ser pura: eis a questo

. . . . . . . . . . . . . . . . . . . . . . 178

Entrada e Sada em Haskell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179


7.2.1

Operaes de entrada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180

7.2.2

Operaes de sada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180

7.2.3

O comando do . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181

Arquivos, canais e descritores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182


7.3.1

A necessidade dos descritores . . . . . . . . . . . . . . . . . . . . . . . . . 184

7.3.2

Canais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186

7.4

Gerenciamento de excees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186

7.5

Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192

Referncias Bibliogrficas

192

A Algumas funes padres

197
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
Functional languages provide a framework
in which the crucial ideas of modern programming
are presented in the clearest possible way.
(Simon Thompson in [46])
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 telefnicas 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. Normalmente, 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:
Compatibilidade. Atualmente, os sistemas no so mais construdos monoliticamente,
como eram no passado. Os programas se tornaram grandes (programming in the large)
e agora eles devem ser escritos de forma modular por programadores em tempos e locais
possivelmente distintos, devendo serem ligados atravs de interfaces bem definidas.
necessrio acabar com o isolamento das linguagens funcionais e incorporar a elas facilidades para a comunicao entre programas funcionais e programas codificados em outras
linguagens pertencentes a outros paradigmas. A Indstria da Computao est comeando
a distribuir padres como CORBA e COM para suportar a construo de software a partir de componentes reutilizveis. Algumas linguagens funcionais j apresentam facilidades
para a construo de grandes softwares, com formas bem adequadas para a definio de
mdulos, apesar de algumas delas ainda no oferecem estas facilidades. Atualmente, os
programas em Haskell j podem ser empacotados como um componente COM e qualquer
componente COM pode ser chamado a partir de Haskell.
Bibliotecas. Muitos usurios escolheram Tcl, atrados, principalmente, pela biblioteca
grfica Tk. Muito pouco da atratividade de Java tem a ver com a linguagem em si, e
sim com as bibliotecas associadas, usadas na construo de grficos, banco de dados, interfaceamento, telefonia e servidores. Apesar de ainda no existirem muitas bibliotecas
1

Philip Wadler trabalha nos grupos de ML e de Unix na Bell Labs. Ele co-autor das linguagens Haskell e
GJ. Alm de vrios artigos publicados, ele tambm co-editor da revista Journal of Functional Programming.

grficas para as linguagens funcionais, muito esforo tem sido feito nesta direo, nos ltimos 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 Dockins. 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 gerador de parsers LALR, similar ao yacc, atualmente extendido para produzir parsers LR
para gramticas ambguas.
Portabilidade. Inegavelmente C e C++ tm sido preferidas em muitos projetos. No
entanto, muito desta preferncia no se deve ao fato de C gerar um cdigo mais rpido
que o cdigo gerado pelas linguagens funcionais, apesar de, normalmente, se verificar esta
diferena de desempenho. Na realidade, esta preferncia se deve mais portabilidade
inegvel de C. Sabe-se que os pesquisadores em Lucent preferiam construir a linguagem
PRL, para Banco de Dados, usando SML, mas escolheram C++, porque SML no estava
disponvel no mainframe Amdahl, onde deveria ser utilizada. Por outro lado, as tcnicas de
implementao de linguagens utilizando mquinas abstratas tm se tornado muito atrativas
para linguagens funcionais [22] e tambm para Java. Isto se deve muito ao fato de que
escrever a mquina abstrata em C a torna muito mais fcil de ser portada para uma grande
variedade de arquiteturas.
Disponibilidade. Alguns compiladores so muito difceis de serem instalados. Por exemplo, a instalao de GHC (Glasgow Haskell Compiler) era considerado uma aventura para
quem tentasse faz-lo. Ainda existem poucas linguagens funcionais comerciais e isto torna
difcil um cdigo estvel e um suporte confivel. Alm do mais, as linguagens funcionais
esto em permanente desenvolvimento e, portanto, esto sempre em transformaes. No
entanto, este quadro tem se modificado, pelo menos em relao a Haskell. Em 1998, foi
adotado o padro Haskell98 que permanece inalterado at o momento, apesar de continuar
a serem incorporadas novas extenses sua biblioteca. Atualmente, a instalao dos compiladores Haskell uma tarefa possvel de ser feita sem qualquer trauma estando disponvel
para vrias plataformas.
Empacotamento. Muitas linguagens funcionais seguem a tradio de LISP, de sempre
realizar suas implementaes atravs do loop read-eval-print. Apesar da convenincia,
essencial desenvolver habilidades para prover alguma forma de converso de programas
funcionais em programas de aplicao standalone. Muitos sistemas j oferecem isto, no
entanto, incorporam o pacote de runtime completo biblioteca e isto implica na exigncia
de muita memria.
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.
Treinamento. Para programadores imperativos muito difcil programar funcionalmente.
Uma soluo imperativa mais fcil de ser entendida e de ser encontrada em livros ou
artigos. Uma soluo funcional demora mais tempo para ser criada, apesar de muito mais
elegante. Por este motivo, muitas linguagens funcionais atuais provem um escape para o
estilo imperativo. Isto pode ser verificado em ML que no considerada uma linguagem
funcional pura, porque permite atribuies destrutivas. Haskell uma linguagem funcional
2

pura, mas consegue imitar as atribuies das linguagens imperativas utilizando uma teoria
funcional complexa que a semntica de aes, implementadas atravs de mnadas2 .
Popularidade. Se um gerente escolher uma linguagem funcional para ser utilizada em
um projeto e este falhar, provavelmente ele ser crucificado. No entanto, se ele escolher C
ou C++ e no tiver sucesso, tem a seu favor o argumento de que o sucesso de C++ j foi
verificado em inmeros casos e em vrios locais. Este quadro tambm vem se modificando
em relao s linguagens funcionais, principalmente Haskell, onde ela tem se popularizado
muito nos ltimos anos.
Desempenho. H uma dcada atrs, os desempenhos dos programas funcionais eram
bem menores que os dos programas imperativos, mas isto tem mudado muito ultimamente.
Hoje, os desempenhos de muitos programas funcionais so melhores ou pelo menos esto
em ps de igualdade" com seus correspondentes em C. Isto depende da aplicao. Java
tem uma boa aceitao e, no entanto, seu desempenho muito inferior a C, na grande
maioria das aplicaes. Na realidade, existem linguagens com alto desempenho que no
so muito utilizadas e existem linguagens com desempenho mediano com alta taxa de
utilizao. Desempenho um fator importante, mas no tem se caracterizado como um
fator decisivo na escolha de uma linguagem.
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 linguagens 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]:
Novidade. Haskell, provavelmente, seja diferente de qualquer linguagem utilizada pelo
leitor. por oferecer um nova forma de pensar sobre software. Em Haskell, se enfatiza o
uso de funes que tomam valores imutveis como entrada e produzem novos valores como
sada, ou seja, sendo dadas as mesmas entradas, as funes devolvem sempre os mesmos
resultados.
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 comportamentos, 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.

Resumidamente, Haskell uma linguagem que representa uma novidade, onde o leitor convidade 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:
Os projetos ASIC e FPGA (Lava, produtos da Bluespec, Inc.).
Software para composio musical (Hascore).
Compiladores e ferramentas relacionadas a eles (GHC).
Controle de reviso distribuda (Darcs).
Middlewere para Web (HAppS, produtos da Galois, Inc.).
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.
Anygma. Uma companhia que desenvolve ferramentas para a criao de multimdia.
Amgen. Uma companhia de biotecnologia que cria modelos matemtidos e outras aplicaes complexas.
Bluespec. Um projeto de software ASIC e FPGA.
Eaton. Usa Haskell no projeto e verificao de sistemas de veculos hidrulicos hbridos.
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.

A crise do software dos anos 80 e a programao estruturada


At os anos 80, os programas de computadores eram pequenos porque os problemas que eles resolviam computacionamente tambm eram pequenos. Este tipo de programao ficou conhecido
como programming in the small. O aumento de desempenho verificado nos computadores proporcionou 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]:
1. Transparncia de significado. Este princpio afirma que o significado de uma expresso,
como um todo, pode ser entendido em termos dos significados de suas sub-expresses.
Assim, o significado da expresso E + F depende simplesmente dos significados das subexpresses E e F, independente das complicaes de cada uma delas.
2. Transparncia de propsitos. Textualmente, Hoare argumenta: o propsito de cada
parte consiste unicamente em sua contribuio para o propsito do todo. Assim, em E
+ F, o nico propsito de E computar o nmero que ser o operando esquerdo do
operador +, valendo o mesmo para o operando F. Isto significa que seu propsito no
inclui qualquer efeito colateral.
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.
4. Aplicaes recursivas. Este princpio se refere ao fato de que as expresses aritmticas
so construdas pela aplicao recursiva de regras uniformes. Isto significa que se ns
sabemos que E e F so expresses, ento sabemos que E + F tambm uma expresso.
5. Interfaces pequenas. As expresses aritmticas tm interfaces pequenas, porque cada
operao aritmtica tem apenas uma sada e apenas uma ou duas entradas. Alm disso,
cada uma das entradas e sadas um valor simples. Assim, a interface entre as partes
clara, pequena e bem controlada.
6. Estruturas manifestas. Este princpio se refere ao fato de que os relacionamentos estruturais entre as partes de uma expresso aritmtica seja bvio. Uma expresso uma
sub-expresso de uma expresso se estiver textualmente envolvida nela. Tambm duas
expresses no esto estruturalmente relacionadas se elas no se sobrepuzerem de alguma
forma.
Estas caractersticas so verdadeiras em relao programao estruturada, no entanto, nem
sempre elas so assim entendidas. A este respeito, John Hughes fez algumas reflexes, analisando a importncia da programao estruturada [13], fazendo um paralelo com as linguagens
funcionais. Em sua anlise, ele cita que quando se pergunta a algum o que programao
estruturada, normalmente se tem, uma ou mais, das seguintes respostas:
uma programao sem gotos,
uma programao onde os blocos no tm entradas ou sadas mltiplas ou
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 responsvel 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, independente do paradigma ao qual pertenam, foram projetadas ou adaptadas depois para serem
modulares.
O aumento de produtividade na programao estruturada se verifica porque:
mdulos pequenos podem ser codificados mais facilmente e mais rapidamente,
mdulos de propsito geral podem ser reutilizados, ou seja, maior produtividade e
os mdulos podem ser testados e compilados de forma independente, facilitando muito a
depurao dos programas.
Vamos agora conhecer a programao funcional para podermos estabelecer uma comparao
entre ela e a programao estruturada.

O que uma linguagem funcional?


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 os programas nela codificados consistem inteiramente de funes,
uma linguagem que no tem side effects,
uma linguagem em que a ordem de execuo irrelevante, ou seja, no precisa analisar
o fluxo de controle,
uma linguagem onde pode-se substituir, a qualquer tempo, variveis por seus valores
(transparncia referencial) ou
uma linguagem cujos programas nela escritos so mais tratveis matematicamente.
Estas respostas tambm no so conclusivas, da mesma forma que as respostas dadas
questo sobre o que programao estruturada. E qual seria uma resposta afirmativa e definitiva,
neste caso? Usando a resposta anterior como base, pode-se afirmar que as linguagens funcionais
so altamente modularizveis.
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 modularidade, 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.

O processo de compilao de linguagens


Programar modelar problemas do mundo real ou imaginrio atravs de uma linguagem de
programao, onde o resultado seja um programa que seja executado em um computador. Para
isso, os problemas devem ser modelados em um nvel de abstrao bem mais alto atravs de
especificaes formais, feitas utilizando uma linguagem de especificao formal. Existem vrias
linguagens de especificao formal: Lotos, Z, VDM, Redes de Petri, entre outras. A escolha de
uma delas est diretamente ligada ao tipo da aplicao e experincia do programador. Por
exemplo, para especificar dispositivos de hardware mais natural se usar Redes de Petri, onde
pode-se verificar a necessidade de sincronizao e podem ser feitas simulaes para a anlise de
desempenho ou detectar a existncia, ou no, de inconsistncias.
Muitas ferramentas grficas j existem e so utilizadas na anlise de desempenho de dispositivos especificados formalmente e podem ser feitos prottipos rpidos para se verificar a
adequabilidade das especificaes s exigncias do usurio, podendo estes prottipos serem parte
integrante do contrato de trabalho entre o programador e o contratante. Mais importante que
isto, a especificao formal representa um prova da corretude do programa e que ele faz exatamente o que foi projetado para fazer. Isto pode ser comparado com a garantia que um usurio
tem quando compra um eletrodomstico em uma loja. Esta garantia no pode ser dada pelos
testes, uma vez que eles s podem verificar a presena de erros, nunca a ausncia deles. Os testes
representam uma ferramenta importante na ausncia de uma prova da corretude de um programa, mas no representam uma prova. O uso de testes requer que eles sejam bem projetados
e de forma objetiva, para que tenham a sua existncia justificada. Com a especificao pronta,
ela deve ser implementada em uma linguagem de programao.
A linguagem de programao a ser escolhida depende da aplicao. Em muitos casos, este processo to somente uma traduo, dependendo da experincia do programador com a linguagem
de programao escolhida. No caso das linguagens funcionais, este processo muito natural, uma
vez que as especificaes formais so apenas definies implcitas de funes, restanto apenas a
traduo destas definies implcitas para definies explcitas na linguagem funcional escolhida3 .
Resta agora a traduo destes programas em linguagens de alto nvel para programas executveis
em linguagem de mquina, sendo este o papel do compilador. Este processo est mostrado na
Figura 1.
A compilao de programas codificados em linguagens imperativas, normalmente, feita em
3

Estas formas de definio de funes sero mostradas no Captulo 1 desta Apostila.

Problemas
Linguagemde
especificaoformal
Especificao
formal
Linguagemdeprogramao
dealtonvel
Programaem
linguagemdealtonvel
Biblioteca

Compilador

Programaem
linguagemdemquina
Entradade
dados

Resultados

Figura 1: Esquema de soluo de problemas atravs de computadores.

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.

Figura 2: Esquema de compilao das linguagens imperativas.

Esta mesma metodologia foi tentada por alguns pesquisadores na traduo de programas codificados 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


Como j mencionado anteriormente, as linguagens funcionais apresentam caractersticas importantes, como as funes de alta ordem, polimorfismo e lazy evaluation. Estas caractersticas
so proporcionadas por particularidades apresentadas pela programao aplicativa ou funcional.
Estas particularidades so: a transparncia referencial, a propriedade de Church-Rosser, a independncia na ordem de avaliao e as interfaces manifestas, que sero objeto de estudo no
Captulo 1 desta Apostila. Para que estas particularidades estejam presentes, necessrio que,
durante a execuo, muitas estruturas permaneam ativas na heap para que possam ser utilizadas
mais tarde. Estas caractersticas tm dificultada a criao de cdigos executveis enxutos e de
bons desempenhos.
Por este motivo, outras tcnicas de implementao foram pesquisadas. Uma que tem apresentado resultados promissores consiste na traduo de programas codificados em linguagens
funcionais para programas em uma linguagem intermediria, como na traduo das linguagens
imperativas, mas utilizando -clculo4 como linguagem intermediria, em vez da linguagem Assembly [22]. J existem mtodos eficientes de traduo de programas codificados no -clculo
para programas em linguagem de mquina. Dessa forma, o problema agora se restringe traduo
dos programas, escritos nas linguagens funcionais, para programas codificados em -clculo. Este
processo est mostrado na Figura 3.

Figura 3: Um esquema de compilao das linguagens funcionais.


A escolha do -clculo como linguagem intermediria entre as linguagens funcionais e a
linguagem de mquina se deve a dois fatores [32]:
1. o -clculo uma linguagem simples, com poucos construtores sintticos e semnticos e
2. o -clculo uma linguagem suficientemente poderosa para expressar todos os programas
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.

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 abstrata 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.

Figura 4: O esquema de compilao de linguagens funcionais adotado em CMC.


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 reconhecidamente 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:
A principal fonte de recursos o site oficial de Haskell: http://www.haskell.org/. Apresenta
muitos links para vrias comunidades e atividades relacionadas ao tema.
Listas de discusso em Haskell: http://haskell.org/haskellwiki/Mailing_lists. O mais interessante o haskell-cafe, onde profissionais e admiradores de vrios nveis se encontram.
Para um chat de tempo real, o canal IRC Haskell: http://haskell.org/haskellwiki/IRC/channel.
nomeado por #Haskell.
Muitos grupos de usurios, workgroups acadmicos: http://haskell.org/haskellwiki/User_groups.
O jornal semanal de Haskell: http://sequence.complete.org/. Mostra um sumrio semanal
das atividades da comunidade de Haskell, com vrios links para listas de discusses.
O Haskell Communities and Activities Report: http://haskell.org/communities/. Coleciona informaes sobre pessoas que usam Haskell e o que elas esto fazendo.

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:
os programas so mais fceis de serem entendidos,
os programas podem ser derivados mais sistematicamente e
mais fcil de serem feitas inferncias sobre eles.
As vantagens da programao funcional sobre a programao imperativa podem ser resumidas
nos seguintes argumentos:
1. A programao funcional conduz a uma disciplina que melhora o estilo.
2. A programao funcional encoraja o programador a pensar em nveis mais altos de abstrao, atravs de mecanismos como funes de alta ordem, lazy evaluation e polimorfismo.
3. A programao funcional representa um paradigma de programao para a computao
massivamente paralela, pela ausncia de atribuies, pela independncia da ordem de avaliao e pela habilidade de operar estruturas complexas de dados.
4. A programao funcional uma aplicao da Inteligncia Artificial.
5. A programao funcional importante na criao de especificaes executveis e na implementao de prottipos com uma rigorosa fundamentao matemtica. Isto permite
verificar se as especificaes esto corretas, ou no.
11

6. A programao funcional est fortemente acoplada Teoria da Computao.


Esta fundamentao matemtica tem como vantagem, primeiramente a constatao de que
essas linguagens tm um embasamenteo terico e depois, sendo matemtico, torna estas linguagens mais tratveis e mais fceis de terem seus programas provados.

Composio desta Apostila


Esta Apostila composta desta Introduo, 8 (oito) Captulos, as referncias bibliogrficas consultadas 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 linguagens 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 principais 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 importncia 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 importante 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 simplificado 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 mostrados 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 juntamente 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
We can now see that in a lazy implementation
based on suspensions, we can treat every function in the same way.
Indeed, all functions are treated as potentially non-strict
and their argument is automatically suspended.
Later, is and when it is needed, it will be
unsuspended (strictly evaluated).
(Antony D. T. Davie in [8])

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.

1.2

Computabilidade de funes

Na dcada de 1930, existiram muitas tentativas de formalizao do construtivismo, procurando


caracterizar o que era computabilidade efetiva, ou seja, procurava-se saber o que realmente podia
ser computado. Uma das mais famosas tentativas foi a definio de Turing sobre uma classe
de mquinas abstratas, que ficaram conhecidas como mquinas de Turing, que realizavam
operaes de leitura e escritas sobre uma fita de tamanho finito. Outra tcnica, baseada mais
diretamente nos trabalhos de Skolen e Peano, consistia no uso de funes recursivas gerais,
devida a Gdel. Uma outra tcnica, com implicao importante na programao funcional, foi
a criao do -clculo, desenvolvido por Church e Kleene, no incio da dcada de 1930. Outra
noo de computabilidade, conhecida como Algoritmos de Markov, tambm foi desenvolvida
nesta mesma poca. O mais importante foi que todas estas noes de computabilidade foram
provadas serem equivalentes. Esta equivalncia levou Church, em 1936, a propor o que ficou
conhecida como a Tese de Church 2 , onde ele afirmava que uma funo era computvel se ela
fosse primitiva recursiva [6].
Isto significa que, j na dcada anterior dcada da inveno do computador eletrnico,
muitos matemticos e lgicos j haviam investigado, com profundidade, a computabilidade de
funes e identificado a classe das funes computveis como a classe das funes primitivas
recursivas.
Em 1958, John McCarthy investigava o uso de operaes sobre listas ligadas para implementar um programa de diferenciao simblica. Como a diferenciao um processo recursivo,
1

a primeira letra do alfabeto hebraico, conhecida por Aleph.


Na realidade, no se trata de uma tese, uma vez que ela nunca foi provada. No entanto, nunca foi exibido um
contra-exemplo, mostrando que esta conjectura esteja errada.
2

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 listas. 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 programao 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 funcional, 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.

1.3

Anlise de dependncias

Segundo MacLennan [25], as linguagens de programao se enquadram em um de dois mundos:


o mundo das expresses (aritmticas, relacionais, booleanas, etc) e o mundo das atribuies. No
primeiro caso, o nico objetivo encontrar o valor de uma expresso atravs de um processo
de avaliao". J no mundo das atribuies, o processo predominante a alterarao" de
alguma coisa, sendo dividido em dois tipos. No primeiro tipo, se altera o fluxo da execuo
de um programa, usando comandos de seleo, como if, for, while, repeat, goto e chamadas a
procedimentos. No segundo tipo, o que se altera o estado da memria (principal ou secundria)
do computador.
No mundo das atribuies, a ordem em que as coisas so feitas tem importncia fundamental.
Por exemplo, a seqncia
i = i + 1;
a = a i;
tem um efeito diferente se forem consideradas as mesmas instrues em ordem invertida, ou seja:
a = a i;
i = i + 1;
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;

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 realizadas 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 inseguro 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.

1.4

Funes e expresses aplicativas

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)
em um contexto em que b = 3 e c = 2. A funo f pura porque definida em termos de
funes puras, que so as operaes aritmticas de adio e multiplicao. Vamos considerar a
avaliao de f (3ax) em um contexto em que a = 2 e x = 3. Como a avaliao de expresses
aritmticas independe da ordem de avaliao, assim tambm ser a avaliao de f . Isto significa
que o argumento (3ax) de f pode ser avaliado antes ou depois da substituio; o resultado ser
sempre o mesmo. Se ele for avaliado antes, a seqncia de avaliaes ser:
f (3ax) = f (3 2 3) = f (18) = (18 + b) (18 + c) = . . . = 21 16 = 336
Por outro lado, se a avaliao de 3ax for deixada para ser feita aps a substituio, a seqncia
de avaliaes ser:
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 imperativa 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:
<EA> ::= <id> (<EA>, ...)
| <literal>
| <id>
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.

1.4.1

Independncia da ordem de avaliao

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 aritmticas. 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 utilizado. 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.

Figura 1.2: Representao do processo de avaliao.


fcil entender esta independncia da ordem de avaliao, observando a rvore de avaliao.
A avaliao iniciada com a colocao de alguns valores nas folhas. Os ns internos so avaliados
3

Uma expresso dita pura quando no realiza qualquer operao de atribuio, explcita ou implcita.

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 avaliao, 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);
}
Como fun no executa qualquer atribuio, a no ser a pseudo-atribuio a fun do valor
de retorno da funo, ela uma funo pura. Isto significa que, na avaliao da expresso
a+2*fun(b), pode-se avaliar primeiro a sub-expresso a ou 2*fun(b), que o resultado ser o
mesmo. No entanto, vamos supor a funo fun1 definida da seguinte forma:
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.

1.4.2

Transparncia referencial

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

Figura 1.3: Grafo com um n compartilhado.


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 expresso 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 importncia 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.

1.4.3

Interfaces manifestas

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 expresses 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.

1.5

Definio de funes

A programao funcional usando a linguagem Haskell o principal objetivo desta Apostila.


Isto significa que devemos estudar formas de passagens das funes matemticas para funes
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.

1.5.1

Definies explcitas e implcitas de variveis

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 interpretadas 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 variveis forem individualmente explcitas e
as equaes puderem ser ordenadas, de forma que nenhuma delas use em seu lado direito
uma varivel j definida anteriormente na lista.
Por exemplo, o conjunto de equaes

y =2ax

x=2

a=3

define explicitamente y, x e a. As equaes precedentes podem ser convertidas s seguintes


regras de reescrita:

y 2ax

x2

a3

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 simultneas. 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.

1.5.2

Definies explcitas e implcitas de funes

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 resolvidas 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 implcita.
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 Tabelaverdade.
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
elas podem ser convertidas s seguintes regras de reescritas:
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

Para a Matemtica, uma funo uma associao de valores pertencentes a um conjunto de


partida, o domnio, com valores pertencentes a um conjunto de chegada, o contra-domnio ou
imagem da funo. Com esta definio em mente, uma funo pode ser representada de duas
maneiras. A primeira delas exibir todos os pares do tipo (entrada, sada), sendo esta uma
definio extensionista, tambm conhecida como definio por enumerao, uma vez que todos os
seus elementos so exibidos. importante caracterizar o domnio e o contradomnio da funo.
Por exemplo, a funo booleana not tem como domnio e contra-domnio o conjunto Bool =
{T rue, F alse} e pode ser definida, por extenso ou enumerao, da seguinte forma:
not : Bool Bool
not (T rue) = F alse
not (F alse) = T rue
De forma similar, as funes or (disjuno) e and (conjuno) tambm podem ser definidas
por enumerao, da seguinte maneira:
or
or
or
or
or

1.5.4

: Bool Bool Bool


and : Bool Bool Bool
(F alse, F alse) = F alse
and (F alse, F alse) = F alse
(F alse, T rue) = T rue e and (F alse, T rue) = F alse
(T rue, F alse) = T rue
and (T rue, F alse) = F alse
(T rue, T rue) = T rue
and (T rue, T rue) = T rue

Definio de funes por intencionalidade

No difcil entender que as definies de funes por enumerao s tm sentido se o domnio


e o contra-domnio forem finitos e de cardinalidade pequena. A grande maioria das funes no
pode ser definida desta forma.
Uma outra maneira de representar uma funo exibir uma propriedade que apenas os
elementos desta funo a tm. Isto significa a exibio de uma regra que informa como cada
elemento do conjunto de partida (domnio) deve ser processado para que ele se transforme em um
nico elemento do conjunto de chegada (imagem). Esta forma de representao de uma funo
conhecida como intencionista.
Por exemplo, a funo unria f que associa cada nmero inteiro com a sua quarta potncia.
O domnio e o contradomnio desta funo so Z e N , respectivamente. A definio de f a
seguinte:
f :ZN
f (n) = n4
Outro exemplo a funo ternria g que associa a cada tripla de nmeros inteiros a soma
de seus quadrados. O domnio de g o produto cartesiano Z Z Z e seu contradomnio N .
Desta forma, a funo fica definida da segunte forma:
g :Z Z Z N
g(x, y, z) = x2 + y 2 + z 2 .

1.5.5

Definio de funes por composio

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.

1.5.6

Definio de funes por casos

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

se x > 0
1,
0,
se x = 0
sinalg(x) =

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

1.5.7

Definio de funes por recurso

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,

n,

mn=

se
se
n + n,
se
n + n + n, se
..
.

m=0
m=1
m=2
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
Neste caso, uma avaliao de 2 3 feita por substituio da seguinte forma:
23 3+(21)3 3+13 3+3+(11)3 6+(11)3 6+03 6+0 6
A recurso o mtodo bsico de se fazer alguma operao iterativamente.
Exerccios resolvidos
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 +

se x = y
x,
mdc(x y, y), se x > y
mdc(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 +

se x = y
1,
1 + div((x y), y), se x > y
div(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 +

se x = y
0,
mod((x y), y), se x > y
mod(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 +

se n < 2k
0,
1 + quantdiv(n, k + 1), se mod(n, k) = 0
quantdiv(n, k) =

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
1. Avalie a expresso 3 5, usando a definio recursiva da multiplicao mostrada nesta
seo.
2. Defina, recursivamente, em termos da multiplicao definida nesta seo, a funo potncia, onde uma base elevada a um expoente inteiro no negativo.
3. Defina, recursivamente, a exponenciao de nmeros no negativos elevados a uma potncia
inteira. Sugesto: use a definio condicional e a resposta do exerccio anterior.
4. Defina, recursivamente, a adio de inteiros no negativos, em termos das funes suc e
pred, onde suc(n)=n+1 e pred(n)=n-1, para n Z.
5. Defina, recursivamente, a adio e a subtrao de inteiros quaisquer, em funo das funes
pred e suc do exerccio anterior.
6. Dado um nmero natural n > 0, n dito perfeito se a soma de seus divisores, incluindo o
nmero 1, igual ao prprio n. O primeiro nmero natural perfeito o nmero 6, porque
6=1+2+3. Defina uma funo eperf eito(n) que informe se n , ou no, um nmero perfeito.
7. Dado um nmero natural n > 0, defina uma funo eprimo(n) que informe se n , ou no,
um nmero primo.
8. Considere o algoritmo a seguir que gera uma seqncia de nmeros naturais no nulos,
a partir de um nmero natural n > 0. Se n for par, divida-o por 2. Se n for mpar,
multiplique-o por 3 e some 1. Repita este processo com o novo valor de n, at que ele seja
igual a 1, se possvel. Por exemplo, para n = 22, a seqncia : 22, 11, 34, 17, 52, 26,
13, 40, 20, 10, 5, 16, 8, 4, 2 e 1. Para cada n, define-se o tamanho do ciclo de n como a
quantidade de nmeros da seqncia gerada, incluindo o nmero 1. No exemplo acima, o
tamanho do ciclo para n = 22 16. Defina uma funo tamciclo(n) que d como resultado
o tamanho do ciclo de n.
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
Type theories in general date back
to the philosopher Bertrand Russell and beyond.
They were used in the early 1900s for the very specific
purpose of getting round the paradoxes that had shaken
the foundations of mathematics at that time,
but their use was later widened until they came to be part of
the logicians standard bag of techinica tools, especially in proof-theory.
(J. Roger Hindley in [10])

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 Schnfinkel, 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 estudos, 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 formalizao adequada do conceito de computabilidade efetiva. Em 1936, Allan Turing modelou a
33

computao automtica e mostrou que a noo resultante (computabilidade de Turing) equivalente 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

O -clculo tem apenas quatro construes: variveis, constantes, aplicaes e abstraes.


Suponhamos que sejam dadas uma seqncia infinita de smbolos distintos chamados de variveis e uma seqncia finita, infinita ou vazia de smbolos distintos, chamados constantes [32].
Quando a seqncia de constantes for vazia, o sistema chamado puro; em caso contrrio,
chamado aplicado. O conjunto de expresses, chamadas de -expresses, definido indutivamente da seguinte forma:
todas as variveis e constantes so -expresses (chamadas de tomos);
sendo M e N duas -expresses, ento (M N ) uma -expresso, chamada combinao ou
aplicao;
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.
1. As variveis so representadas por letras romanas minsculas.
2. As -expresses completas so representadas por letras romanas maisculas ou por letras
gregas minsculas (exceto , , e que tm significados especiais) ou por letras gregas
maisculas.
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 aplicaes de termos combinados so associados pela esquerda, ou seja, E1 E2 E3 . . . En significa (. . . ((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.
4. A escolha de constantes, de funes embutidas para serem utilizadas na manipulao destas
constantes e/ou nmeros e das funes para o processamento de listas arbitrria.
Exemplos
So exemplos de -expresses:
1.
2.
3.
4.
5.
6.
7.

2.3

z
8
zy
(a . (ab))
(y . y) (a . (ab))
(a (z . y))a
(a . b . c . (ac)) (k . k)(x . x)

uma
uma
uma
uma
uma
uma
uma

varivel
constante
combinao
-abstrao
combinao
combinao
combinao

ou
ou
ou
ou
ou

aplicao ((z)(y))
funo
aplicao
aplicao
aplicao

A sintaxe do -clculo

Nesta seo ser mostrada a sintaxe do -clculo, ficando a semntica para uma prxima. A
sintaxe importante na construo de -expresses de forma correta.

2.3.1

Funes e constantes pr-definidas

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:
as funes aritmticas (+, -, *, /) e as constantes 0, 1, . . . e 9;
as funes lgicas (AND, OR, NOT) e as constantes TRUE e FALSE;
os caracteres constantes (a, b, ...);
a funo condicional IF: IF TRUE E1 E2 E1
IF FALSE E1 E2 E2
as funes CONS, HEAD e TAIL, onde
HEAD (CONS a b) a
TAIL (CONS a b) b;
a constante NIL, a lista vazia.
A escolha de funes embutidas arbitrria e, assim sendo, elas sero adicionadas medida
que as necessidades surgirem. Deve ainda ser mencionado que:
todas as aplicaes de funes so pr-fixas. Por exemplo, a expresso (+( 2 3) ( 8 2))
tem seus parnteses externos redundantes podendo, e at devendo, serem retirados para
evitar confuso visual;
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

As funes embutidas no -clculo so formalizadas como j mostrado anteriormente. As abstraes so funes no embutidas e so construdas atravs do construtor (). Por exemplo,
(x . + x 1) uma -abstrao e deve ser interpretada da seguinte forma:

x
.
+x1

indica que se trata de uma funo


sobre a varivel x
que
adiciona x ao nmero 1.

Uma -abstrao tem sempre estes 4 (quatro) elementos: o construtor (), o parmetro
formal (uma varivel, no caso, x) o ponto (.) e o corpo da funo (+ x 1). Uma -abstrao
pode ser comparada com as funes em uma linguagem de programao imperativa. Por exemplo,
a -abstrao anterior pode ser representada em C pelo seguinte fragmento de cdigo:
int inc (x)
int x;
{ return (x + 1);

Deve ser observado que as funes em uma linguagem de programao convencional tm,
obrigatoriamente, um nome, enquanto, no -clculo, as -abstraes so funes annimas.

2.3.3

Aplicao de funo e currificao

No -clculo, a aplicao de uma funo f a um parmetro x denotada por justaposio, ou


seja, f x . E se a funo tiver mais de um argumento? A -expresso + 3 4 interpretada como
(+ 3) 4, ou seja, como uma funo (+ 3) que adiciona 3 ao argumento (4). Esta propriedade se
deve ao fato de que o -clculo permite que o resultado da aplicao de uma funo seja tambm
uma outra funo.
Esta propriedade foi descoberta por Schnfinkel e foi amplamente utilizada por Curry. Por
este motivo, passou a ser conhecida como currificao que uma caracterstica de algumas
linguagens, onde uma funo com n argumentos pode ser interpretada como n funes de apenas 1
(um) argumento. Isto significa que, para o -clculo, todas as funes tm apenas um argumento.

2.4

A semntica operacional do -clculo

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

De maneira similar, a integral seguinte, em relao varivel ligada x


Z 1

x2 3xdx

representa a mesma integral, a seguir, com respeito varivel ligada t.


Z 1
0

t2 3tdt

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,
x[x + 1 > x] y[y + 1 > y]
Vejamos agora, uma outra expresso, tambm envolvendo somatrio:
i

n
X

(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
i

n
X

(k 2 + i a)

k=1

tem, exatamente, o mesmo significado da anterior. Em uma expresso, uma ocorrncia no


ligada de uma varivel dita ser livre. As variveis i, a e n ocorrem livres nesta expresso. Se
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

e ocorre ligada na expresso


m
X
i=1

i!

n
X

j 2 + i a

j=1

O escopo (binding site) de um identificador determina a regio da expresso na qual o identificador ocorre ligado. Esta regio, normamente, indicada por alguma conveno lxica, como
os parnteses, colchetes ou chaves. Esta regio o corpo da expresso, significando que uma
mesma varivel pode ocorrer ligada em um ponto e livre em outro.
Como visto, a troca de variveis ligadas no interfere no significado da expresso. No entanto,
esta troca no pode ser feita por uma varivel que ocorra livre na expresso, porque ela muda
seu significado. Por exemplo, a expresso que representa a soma dos elementos da linha i de uma
matriz Amn o somatrio
n
X

Aij = Ai1 + Ai2 + . . . + Ain

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

cujo significado o mesmo da expresso anterior. No entanto, observemos que a varivel j


no pode ser trocada pela varivel i porque a expresso se tornaria o somatrio de Aii , que
representaria o somatrio dos elementos da diagonal da matriz e no mais dos elementos da
linha i, como inicialmente. Este fenmeno conhecido como coliso de identificadores e deve
ser evitado.
Fazendo um paralelo com as linguagens de programao comuns, as variveis ligadas das
expresses correspondem aos parmetros formais das funes e tm o mesmo status das variveis
locais.
Aps termos visto as noes de ocorrncias ligadas e livres e da coliso de identificadores de
maneira intuitiva, vamos agora formalizar estes conceitos.

2.4.1

Ocorrncias livres ou ligadas

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

Figura 2.1: Ocorrncias livres e ligadas de variveis.


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
b) x livre em y . M
c) x livre em M N

se x = y. Se x 6= y, a ocorrncia de x ligada.
se (x for livre em M ) E (x 6= y)
se (x for livre em M ) OU (x for livre em N )

Exemplos
1. x ocorre livre em x, em xy, em a . xy e em (a . xy) (x . xy).
2. x ocorre ligada em y, em x . xy, em (x . ax) (y), em x . abbx e em (a . xy)(x . xy).

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 devem 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
1. Identifique nas expresses abaixo aquelas que so, ou no, -expresses:
a) a
Sim, a uma varivel;
b) 9
Sim, 9 uma constante;
c) ((b.b)(a.ab)
No, os parnteses no esto aninhados
corretamente;
d) (x.)a
No, x. no uma -abstrao;
e) ((x.y.y)(y.yyy))((i.i)(a.b)) Sim, porque contm apenas aplicaes,
abstraes e variveis.
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

Tabela 2.1: Resumo


Uma ocorrncia de uma varivel deve ser livre ou ligada.
Definio de ocorrncia x ocorre livre em x
livre:
x ocorre livre em (E F ) x ocorre livre em E ou
x ocorre livre em F
x ocorre livre em y . E x e y so variveis distintas e
x ocorre livre em E
Definio de ocorrncia ligada:

x ocorre ligada em (E F ) x ocorre ligada em E ou


x ocorre ligada em F
x ocorre ligada em y . E (x e y so a mesma varivel e
x ocorre livre em E) ou
x ocorre ligada em E.

Exerccios propostos
1. Justifique porque as expresses abaixo no so -expresses:
(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))
3. Quais das seguintes expresses so combinadores?
(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

A forma de avaliao utilizada no -clculo consiste em um processo de transformao de uma


-expresso em outra, normalmente, mais simples. Este processo continuado at atingir uma expresso que no pode mais ser transformada, ou entrar em loop infinito. Esta seo dedicada
a anlise de tcnicas utilizadas para promover estas transformaes. Na realidade elas tm um
papel similar ao que as tcnicas algbricas tm na soluo de equaes na Matemtica.
Existem trs tcnicas bsicas utilizadas na converso de uma -expresso em outra: converso, -converso e -converso.

2.5.1

-converso

Ao analisarmos as duas -expresses (x . + x 1) e (y . + y 1), verificamos que elas apresentam


o mesmo comportamento, ou seja, a nica diferena entre elas est nos nomes dos parmetros,
significando que representam a mesma -expresso. Portanto, que estas duas -expresses devem
ser convertveis uma na outra. Esta converso chamada de -converso e nos permite trocar os
nomes dos parmetros formais (variveis ligadas) de qualquer -abstrao. Este tipo de converso
est de acordo com a mudana de variveis ligadas, j vista anteriormente. Assim, temos:

1. (x. x 1) (y. y 1)

2. x.x y.y z.z

3. x.axa b.aba
A partir da definio de -reduo dada anteriormente, pode-se deduzir as seguintes relaes:

i)

ii)

iii) e =

(reflexo)
(simetria)
(transitividade)

Uma relao matemtica que reflexiva, simtrica e transitiva uma relao de equivalncia". Assim, a -reduo determina uma relao de equivalncia entre duas -expresses, o que
nos permite afirmar que estas duas -expresses representam a mesma entidade. Isto significa
que o usurio pode utilizar uma ou a outra, de acordo com a convenincia que cada caso requer.

2.5.2

-converso

O resultado da aplicao de uma -abstrao a um argumento uma instncia do corpo desta


-abstrao na qual as ocorrncias livres do parmetro formal so trocadas por cpias do argumento. Deve ser observado que uma ocorrncia livre de uma varivel no corpo de uma -abstrao
corresponde a uma ocorrncia ligada desta mesma varivel na -abstrao.
Por exemplo, a aplicao (x . + x 1) 4 reduzida para + 4 1 que uma instncia do corpo
+ x 1, onde trocamos a ocorrncia livre de x pelo argumento 4. Esta operao chamada de
-reduo.
Exemplos:

1. (x . + x x) 5 + 5 5 = 10

2. (x . 3) 5 3

(veja que 5 ocorre livre em + x x)

(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

Analisemos agora as duas -abstraes (x .+ 1 x) e (+ 1). Verifica-se que elas se comportam da


mesma maneira quando aplicadas a um mesmo argumento, ou seja, produzem o mesmo resultado.

Assim, elas tambm devem ser convertveis uma na outra, ou seja, (x . + 1 x) (+ 1).

Formalmente, (x . F x) F , desde que x no ocorra livre em F .


Exemplos:
1. A -expresso (x . + xx) no -redutvel a (+ x) porque a varivel x ocorre livre em
(+ x).

2. A seqncia de redues, a seguir, vlida: x . y . z . xyz x . y . xy x . x.


Intuitivamente, a -reduo reflete um princpio importante na computao, conhecido como
princpio da extensibilidade", utilizado na implementao da igualdade de funes. Este princpio estabelece que duas funes so iguais se elas sempre apresentam os mesmos resultados para
as mesmas entradas". Ele importante porque oferece a possibilidade de escolha entre mais de
um algoritmo para implementar a igualdade entre funes. A escolha deve ser feita levando
em considerao o desempenho das implementaes, um fato que no tem importncia para os
matemticos, mas para os profissionais da Computao, tem um significado fundamental.

2.5.4

Convertibilidade de -expresses

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

Figura 2.2: Interpretao grfica de uma seqncia de -redues.


A Figura 2.2 mostra graficamente uma interpretao para esta definio.
Esta forma de definio de -reduo entre duas -expresses nos permite afirmar que se uma
-expresso se -reduz a uma -expresso 1 e uma outra -expresso tambm se -reduz
a 1 , ento a -expresso tambm se -reduz a . Dito de outra forma, se duas -expresses
distintas se -reduzem a uma terceira, ento elas tambm so -redutveis entre si.
Esta forma de definio muito utilizada em demonstraes de convertibilidade entre expresses e ser utilizada mais adiante, ainda neste Captulo.
Exerccios:

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

(x . + ( x 1))9 3

linha 1
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, tornouse conectado.
Esta situao conhecida como o problema de captura de variveis e pode provocar ambiguidades 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 tradicionais, 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.
1. A seqncia de redues para (x . y . + x((x . x 3)y))5 6 :
(x . y . + x((x . x 3)y))5 6

(y . + 5((x . x 3)y))6

+ 5 ((x . x 3)6)

+ 5 ( 6 3)
=+53
=8
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
Isto significa que no h a necessidade de que os construtores HEAD, CONS, TAIL ou
qualquer funo sejam pr-definidos. Na realidade, eles existem apenas por questo de eficincia.
Resumo
1. Troca de nomes: -converso permite trocar o nome de um parmetro de forma consistente.
2. Aplicao de funo: -converso permite aplicar uma -abstrao a um argumento,
construindo uma nova instncia do corpo da -abstrao.
3. Eliminao de -abstraes redundantes: -reduo pode, algumas vezes, eliminar abstraes redundantes.
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 conversibilidade das duas funes anteriores, utilizando esta tcnica.
Tabela 2.2: Exemplo de aplicaes ao mesmo argumento, w.
IF TRUE ((p . p) 3) w
(p . p) 3

(x . 3) w
pela def de IF

Portanto, IF T RU E ((p . p)3) (x . 3)


Este esquema de prova tem a vantagem de usar apenas reduo e evitar o uso explcito de
-redues.

2.5.7

Uma nova notao

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 substitudas 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) . . .
correspondendo a um loop infinito nas linguagens imperativas.
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 demonstraes, garantem esta propriedade. Sugere-se ao leitor pesquisar estas demonstraes na bibliografia 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
sempre que se tem

X1

podese completar o diamante


X2

X
X2

Figura 2.3: Interpretao grfica da propriedade do diamante.


A partir da propriedade do diamante, o teorema de Church-Rosser descrito da seguinte
forma: a reduo tem a propriedade do diamante.
Um redex dito ser leftmost-outermost em uma -expresso, se todos os outros redexes
desta -expresso estiverem sua direita ou dentro dele. Alm disso, diz-se que uma seqncia
de redues feita na ordem normal se o redex escolhido para execuo em cada estgio for
leftmost-outermost.
Exerccio. Encontre o redex leftmost-outermost nas seguintes -expresses:
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
Teorema 2 de Church-Rosser (CRT II). Se E1 E2 e E2 est na forma normal,
ento existe uma ordem normal de seqncias de reduo de E1 para E2 .
Este teorema tambm conhecido como teorema da normalizao" e significa que existe, no
mximo, um resultado e a ordem normal o encontra, se ele existir. A ordem normal de reduo
especifica que o redex leftmost-outermost deve ser escolhido primeiramente. Dito de outra forma,
a reduo do redex mais externo e mais esquerda, em cada ponto da seqncia de redues,
leva at a forma normal, se ela existir.
Exemplo. Utilize a ordem normal de reduo para determinar a forma normal da -expresso
(x . xx)((y . y)(z . z)).
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].

2.7

Funes recursivas

A idia de escolher o -clculo como linguagem intermediria entre as linguagens funcionais, de


alto nvel, e a linguagem de mquina significa que todas as funes devem ser traduzidas para
ele. Na realidade, os programas funcionais representam apenas uma forma mais adequada de expresses. Diz-se que os programas funcionais so aucaramentos sintticos" de -expresses,
para torn-los mais adequados e mais fceis de serem tratados.
No entanto existe um problema a ser resolvido, relacionado ao fato de que uma das caractersticas notveis dos programas funcionais a utilizao massiva de funes recursivas [32] e estas
funes no tm correspondentes no -clculo, conforme visto at aqui. Isto acontece porque,
no -clculo, as funes so annimas e, portanto, no podem ser chamadas recursivamente.
necessria uma forma de implementar funes recursivas no -clculo. Vamos mostrar como
estas funes so traduzidas para o -clculo, sem a necessidade de qualquer extenso.
Voltemos, momentaneamente, nossa ateno para a definio matemtica da funo f (x) =
x3 x. Na realidade, estamos procurando valores para x e os respectivos resultados da aplicao
da funo f a estes valores. Como um caso particular, vamos procurar valores x que satisfaam
a igualdade f(x) = x, ou seja, estaremos procurando valores de x para os quais x3 x = x. Um
tal valor x = 0, porque f (0) = 0. Mas x = 21/2 so outros valores que tambm satisfazem a
igualdade f(x) = x.
Estes valores so chamados de pontos fixos da funo f e representam um campo importante
de estudo matemtico, destacando-se o teorema do ponto fixo, alm de outros. Os pontos fixos
apresentam caractersticas importantes, no entanto o nosso interesse aqui se prende exclusivamente em utiliz-los na construo de funes recursivas no -clculo.
Considere agora a seguinte definio recursiva da funo fatorial:
FAT = (n . IF (= n 0) 1 (* n (FAT (- n 1)))).
Nesta definio, damos um nome a uma -abstrao (FAT ) e nos referimos a ele mesmo, dentro 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 desenvolvimento. 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.
1. F = f.n.$iszero n $one ($mul n (f ($pred n)))
2. Fv = f.n.$iszero n (d.$one)(d.$mul n (f ($pred n))) (d.d)
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)
7. Y = h.(x.h(x x))(x.h(x x))
8. YT = (x.y.y (x x y)) (x.y.y (x x y))
9. Yp = h.(x.h (a.x x a)) (x.h (a.x x a))
10. Yv = h.(x.a.h (x x)a) (x.a.h (x x)a)
11. add = m.n.f.x.m f (n f x)
12. append = $Y (g.z.w.$null z w ($cons ($hd z) (g ($tl z) w)))
13. cons = x.y.$pair $false ($pair x y)
14. exp = m.n.f.x.m n f x
15. false = x.y.y
16. fst = p.p $true
17. hd = z.$fst ($snd z)
18. iszero = n.n (v.$false) $true
19. mul = m.n.f.x.m (n f )x
20. next = p.$pair ($snd p) ($succ ($snd p))
21. nil = z.z
22. null = $fst
23. pair = x.y.s.s x y
24. pred = n.$fst (n $next ($pair $zero $zero))
25. snd = p.p $false
26. succ = n.f.x.f(n f x)
27. tl = z.$snd ($snd z)
51

28. true = x.y.x


29. zero = f.x.x
30. one = f.x.f x
31. two = f.x.f(f x)
32. three = f.x.f(f(f x))
33. four = f.x.f(f(f(f x)))
34. five = f.x.f(f(f(f(f 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 aprofundados 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

Programao funcional em Haskell


There are many distint pleasures
associated with Computer programming.
Craftsmanship has its quiet rewards, the satisfaction that
comes from building a useful object and making it work.
(Steven S. Skiena et Miguel A. Revilla in [39])

3.1

Introduo

Os Captulos anteriores foram feitos com o objetivo de servirem como preparao e fundamentao 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 linguagem 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 proeminentes 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 linguagens de programao, ou seja, desejava-se que a linguagem estivesse sempre em evoluo. No entanto, 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 incorporadas, 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 compilador 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/ augustss/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.

3.1.1

Outras implementaes

Antes do desenvolvimento de Haskell, existia um grande projeto de pesquisa na Universidade


de Yale envolvendo a linguagem Scheme e um dialeto seu, chamado T. Diversas Disertaes e
Teses foram feitas como resultados deste trabalho, orientadas, principalmente, por Paul Hudak.
O compilador Orbit para T esteve um dos resultados deste esforo. Como Paul Hudak foi
fortemente envolvido no projeto de Haskell, foi natural que as tcnicas aplicadas em Scheme
fossem tambm utilizadas em Haskell. Neste caso, foi mais fcil compilar Haskell em Scheme
ou T e usar o compilador como back end. Infelizmente o compilador T j no era mais mantido
e tinha problemas de desempenho, sendo trocado por uma implementao de Common Lisp,
buscando desempenho e portabilidade. Esta implementao ficou conhecida como Haskell de
Yale, a primeira implementao de Haskell que suportava cdigo compilado e interpretado para
um mesmo programa. A implementao de Haskell de Yale foi abandonada em 1995, quando se
verificou que programas compilados em GHC estavam sendo executados duas a trs vezes mais
rpidos e no existia qualquer perspectiva de mudana neste quadro.
No entanto, uma das principais espectativas no uso de Haskell se encontrava no projeto do
Dataflow do MIT, conduzido por Arvind, cuja linguagem de programao era chamada Id. Em
1993, Arvind e seus colegas resolveram adotar a sintaxe de Haskell e seu sistema de tipos, com
avaliao eager e paralela. A linguagem resultante foi chamada de pH (parallel Haskell ), o tema
do livro de Nikhil e Arvind sobre programao paralela implcita [27]. No entanto, ultimamente,
tm surgido diversas implementaes de Haskell. Entre elas, podem ser citadas:
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/ResearchProjects.
jhc. Este um novo compilador, desenvolvido por John Meacham, focalizado em otimizao agressiva, usando anlise de programas, permitindo uma tcnica deferente na implementao 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 implementao 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.

3.2

Primeiros passos

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)
quadrado :: Int -> Int
quadrado x = x*x
todosIguais :: Int -> Int -> Int -> Bool
todosIguais n m p = (n == m) && (m == p)
{-######################################################################-}
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.
##########################################################################
> valor :: Int -- Uma constante inteira
> valor = 39
> novalinha :: Char
> novalinha = \n
> resposta :: Bool
> resposta = True
> maior :: Bool
> maior = (valor > 71)
> quadrado :: Int -> Int
> quadrado x = x*x
> todosIguais :: Int -> Int -> Int -> Bool
> todosIguais n m p = (n == m) && (m == p)
#########################################################################
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 consideradas 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.

3.2.1

O interpretador ghci

O interpretador ghci disponibiliza a biblioteca de funes pr-definidas que compem o arquivo


Prelude.hs, e podem ser utilizadas pelo usurio a partir do momento em que o interpretador
carregado. Na chamada, aberta uma seo, que permanece ativa enquanto o sistema estiver
em execuo.
Os comandos de ghci so muito simples e no oferecem muitas possibilidades ao usurio.
Eles podem ser vistos pela chamada ao help, atravs do comando :?. Alguns deles podem ser
observados na Tabela 3.1.

Comando
:?
:e
:e exemplo.hs
:l exemplo.hs
:a exemplo.hs
:q

Tabela 3.1: Principais comandos de ghci.


Ao realizada
Aciona o help
Chama o script atual
Edita o arquivo exemplo.hs
Carrega o script exemplo.hs e limpa outros arquivos carregados
Carrega o script exemplo.hs sem limpar os outros arquivos
Termina a seo

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.
Tabela
Prioridade
9
8
7
6
5
4
3
2
1
0

3.2: Tabela de alguns operadores de Haskell, com suas prioridades.


Assoc. esquerda No associativa
Assoc. direita
!, !!, //, > . >
>>=
.
**, ,
% , /, div, mod, rem, quot
+, :+
\\
:, ++, > + >
/=, <, <=, = =, >, >=,
elem, notElem
&&
||
:=
$

Os operadores podem ser infixos ou pr-fixos. Normalmente os operadores aritmticos so


declarados como infixos, por exemplo, se usa a expresso 3 + 7, por ser esta a forma comumente
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

3.2.2

Identificadores em Haskell

Os identificadores em Haskell so sensveis a caracteres, ou seja, as letras maisculas so distintas


das letras minsculas. Os identificadores, devem ser iniciados, sempre, por uma letra maiscula,
se for um tipo, ou minscula, se for um outro identificador como uma varivel, uma constante
ou uma funo. A esta primeira letra do identificador podem ser seguidos outros caracteres, que
podem ser letras maisculas ou minsculas, dgitos, sublinhado ou acentos agudos. Por exemplo,
type Par = (Int, Int)
somaAmbos :: Par -> Int
somaAmbos (primeiro, segundo) = primeiro + segundo
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 apresenta 22 palavras reservadas, mostradas a seguir:
case
class
data
default
deriving

else
hiding
if
import
in

infix
infixl
infixr
instance
let

module
of
renaming
then
to
58

type
where

3.3

Funes em Haskell

As formas de definio de funes em Haskell tm a ver com as formas de definio de funes


utilizadas na Matemtica, mostradas no Captulo 1. Elas podem ser representadas graficamente
por uma caixa que recebe um ou mais parmetros como entrada (argumentos), processa-os e
constri um resultado nico que exibido como sada, conforme pode ser visto na Figura 3.1.

Int
+

Int

Int
Int
Int

todosIguais

Bool

Int
Figura 3.1: Representao grfica das funes + e todosIguais.
Exemplos de funes:
Uma funo que calcula as razes de uma equao bi-quadrada.
Uma funo que emite o relatrio final com as notas parciais e final dos alunos da disciplina
Tpicos em Linguagem de Programao.
Uma funo que controla a velocidade de um automvel.
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:
espelhaV :: Quadro -> Quadro
espelhaH :: Quadro -> Quadro
invertecor :: Quadro -> Quadro
escala :: Quadro -> Quadro
sobrepoe :: Quadro -> Quadro

3.3.1

Construindo funes

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

Figura 3.2: Resultados grficos de funes.


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:
+ :: Int -> Int -> Int e
todosIguais :: Int -> Int -> Int -> Bool
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:
a funo tem trs argumentos de entrada, todos do tipo inteiro (Int) e
o resultado da aplicao da funo um valor do tipo Bool, facilitando o entendimento do
problema e a forma de soluo adotada para resolv-lo.
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.
somaAmbos :: Int -> Int -> Int
somaAmbos x y = 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:
mmc :: Int -> Int -> Int
mmc x y = div x*y (mdc x y)
mdc :: Int -> Int -> Int
mdc x y
|x == y
= x
|x > y
= mdc x-y y
|otherwise = mdc y x
div :: Int -> Int -> Int
div x y
|x == y
= 1
|x > y
= 1 + div x-y y
|otherwise = 0
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 explicitamente 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 explicitamente, como uma disciplina de programao. Isto permite ao programador um completo
entendimento do problema e da soluo adotada.

3.3.2

Avaliao de funes em Haskell

A forma de avaliao de funes utilizada em programas codificados em Haskell a ordem


normal de avaliao, preconizada pelo segundo teorema de Church-Rsser, visto no Captulo
2. Isto significa que Haskell obedece ao sistema leftmost-outermost, usando um mecanismo de
avaliao lazy (preguioso) que s avalia uma expresso se ela for realmente necessria e no
mximo uma vez. Isto significa um tipo de avaliao semelhante avaliao curto-circuito
utilizada em algumas linguagens convencionais. Vejamos um exemplo de avaliao usando as
funes definidas nos scripts mostrados no incio deste Captulo.
todosIguais (quadrado 3) valor (quadrado 2)
= ((quadrado 3) == valor) && (valor == (quadrado 2))
= ((3 * 3) == valor) && (valor == (quadrado 2))
= (9 == valor) && (valor == (quadrado 2))
= (9 == 39) && (39 == (quadrado 2))
= False && (39 == (quadrado 2))
= False (utilizando o mecanismo de avaliao lazy)
61

3.3.3

Casamento de padres (patterns matching)

O casamento de padres outra forma de codificao de funes em Haskell, baseada na definio


por enumerao utilizada na Matemtica, vista no Captulo 1. Neste tipo de definio, so
exibidos todos os valores que os argumentos da funo podem ter e, para cada um deles, declara-se
o valor do resultado correspondente. De forma resumida, exibem-se todos pares do mapeamento
(entrada, resultado). Por exemplo, vejamos as declaraes das funes eZero e fat, a seguir:
eZero :: Int -> Bool
eZero 0 = True
eZero _ = False

fat :: Int -> Int


fat 0 = 1
fat n = n * fat (n - 1)

Na execuo de aplicaes destas funes, os padres so testados seqencialmente, de cima


para baixo. O primeiro padro que casa com o valor da entrada ter o valor correspondente como
resultado da aplicao da funo. Se no ocorrer qualquer casamento entre o valor de entrada e
um padro de entrada, o resultado da aplicao da funo ser um erro. Ao se aplicar a funo
eZero a um argumento n, primeiro verificado se este nmero n casa com 0. Se for verdade, o
resultado ser True. Se este padro no for verificado, ou seja, se n for diferente de 0, verifica-se
o casamento com o segundo padro e assim por diante. Neste caso, o resultado ser False. O
mesmo reciocnio vale para a funo fat.
Esta forma de anlise seqencial deve sempre ser levada em considerao para que erros
grosseiros sejam evitados. Como exemplo, se, na declarao da funo eZero, a definio da
funo para o caso de n ser 0 for trocada pela segunda definio, o resultado da aplicao da
funo ser sempre igual a False, mesmo que o argumento seja 0, uma vez que o _ (undescore)
significa qualquer caso.
Exerccios:
1. D a definio da funo todosQuatroIguais do tipo Int >Int >Int >Int >Bool
que d o resultado True se seus quatro argumentos forem iguais.
2. D definio da funo todosQuatroIguais usando a definio da funo todosIguais,
dada anteriormente.
3. O que est errado com a definio da funo todosDiferentes abaixo?
todosDiferentes n m p = ( (n /= m) && (m /= p) )
4. Projete um teste adequado para a funo todosIguais, considerando a funo
teste :: Int >Int >Int >Int
teste n m p = ((n+m+p) == 3*p).
Esta funo se comporta da mesma forma que a funo todosIguais para o seu teste de
dados? Que concluso voc tira sobre os testes em geral?
5. D uma definio para a funo quantosIguais aplicada a trs entradas inteiras e que
retorna quantas delas so iguais.
6. Escreva a sequncia de clculos para as seguintes expresses:
maximo ((2 + 3) 7) (4 + (1 3))
todosIguais 4 quadrado 2 3
quantosIguais 3 4 3
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.

3.4.1

Os tipos primitivos da linguagem

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.
Tabela 3.3: Operadores aritmticos e relacionais dos tipos Int e Integer.
Operador
Tipo
Descrio
+, *
Int -> Int -> Int
adio e multiplicao

Int -> Int -> Int


exponenciao
Int -> Int -> Int
subtrao (infixa) e inversor de sinal (prefixa)
div
Int -> Int -> Int
diviso inteira (prefixa), ou div (infixa)
mod
Int -> Int -> Int
mdulo (prefixa), ou mod (infixa)
abs
Int -> Int
valor absoluto de um inteiro
negate
Int -> Int
troca o sinal de um inteiro
>, >=, == Int -> Int -> Bool operadores elacionais
/ =, <=, < Int -> Int -> Bool operadores relacionais

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:
Questo 1: Qual o total de vendas desde a semana 0 at a semana n?
Questo 2: Qual a maior venda semanal entre as semanas 0 e n?
Questo 3: Em que semana ocorreu a maior venda?
Questo 4: Existe alguma semana na qual nada foi vendido?
Questo 5: Em qual semana no houve vendas? (se houve alguma).
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.
venda
venda
venda
venda

:: Int -> Int


0 = 7
1 = 2
2 = 5

Verifiquemos agora como as definies recursivas utilizadas na Matemtica podem ser utilizadas em Haskell. Recordemos a definio matemtica de uma funo usando recurso. Por
exemplo, na definio de uma funo fun, devemos:
explicitar o valor de fun 0, (caso base)
explicitar o valor de fun n, usando o valor de fun (n - 1) (caso recursivo ou passo indutivo).
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:
totaldeVendas 2 =
=
=
=
=
=
=
=

totaldeVendas 1 + venda 2
(totaldeVendas 0 + venda 1) + venda 2
(venda 0 + venda 1) + venda 2
(7 + venda 1) + venda 2
(7 + 2) + venda 2
9 + venda 2
9 + 5
14

E se chamarmos totaldeVendas (-2)? O resultado ser = ERROR: Control stack


overflow, uma vez que a pilha do sistema vai estourar. Para esta entrada deve ser criada uma
exceo e, como tal, deve ser tratada. Em Haskell, existem vrias formas de tratar excees, mas
elas sero vistas no Captulo 6. Apenas para exemplificar, uma forma de fazer isto definir um
valor fictcio para o caso de n ser negativo, transformando a definio dada na seguinte:
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.
maiorVenda :: Int -> Int
maiorVenda n
|n == 0
|maiorVenda (n-1) >= venda n
|otherwise

= venda 0
= maiorVenda (n - 1)
= venda n

Esta mesma funo pode ser construda de outra forma, usando uma funo auxiliar, maximo, que aplicada a dois inteiros retorna o maior entre eles.
maximo :: Int -> Int -> Int
maximo x y
|x >= y
= x
|otherwise = y
maiorVenda n
|n == 0
|otherwise

= venda 0
= maximo (maiorVenda (n - 1)) venda n

Esta forma de definio de funes chamada recurso primitiva. De forma resumida, at


agora vimos trs formas de definir funes:
1. quadrado x = x * x
2. maximo n m
| n >= m
| otherwise

= n (equao condicional)
=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.
7. D uma definio de uma funo de m e n que retorna o produto
m * (m + 1) * ... *(n 1) * n, para m < n e retorne 0 se m 0.
8. D uma definio de uma funo que retorne i-simo nmero da sequncia de Fibonacci (0,
1, 1, 2...).
O tipo ponto flutuante (Float ou Double)
Os valores do tipo ponto flutuante (nmeros reais) pertencem aos tipos Float ou Double,
da mesma forma que um nmero inteiro pertence aos tipos Int ou Integer. Isto significa que
as nicas diferenas entre valores destes tipos se verificam na quantidade de bits usados para
represent-los. A Tabela 3.4 mostra as principais funes aritmticas pr-definidas na linguagem.
As funes aritmticas recebem a denominao especial de operadores.
Tabela 3.4: Operadores aritmticos de ponto flutuante em Haskell.
+, - *
Float > Float > Float
/
Float > Float > Float

Float > Int > Float


**
Float > Float > Float
==, /= <, >,<=, >= Float > Float > Bool
abs
Float > Float
acos, asin, atan
Float > Float
ceiling, floor, round
Float > Float
cos, sin, tan
Float > Float
exp
Float > Float
fromInt
Int > Float
log
Float > Float
logBase
Float > Float > Float
negate
Float > Float
read
String > Float
pi
Float
show
* > String
signum
Float > Int
sqrt
Float > Float

Apesar de alguns pesquisadores, mais puristas, condenarem a sobrecarga de operadores, alguns outros defendem que alguma forma de sobrecarga deve existir, para facilitar a codificao de
programas. Os projetistas de Haskell admitiram a sobrecarga de operadores para ser utilizada na
implementao de uma de suas caractersticas importantes e que permite um grau de abstrao
bem maior que normalmente se encontra em outras linguagens. Esta caracterstica se refere s
classes de tipos (type class), um tema a ser analisado no Captulo 5.
Os tipos Int, Integer, Float e Double so os tipos numricos mais conhecidos em Haskell.
No entanto, outros tipos numricos tambm existem em Haskell. A Tabela 3.5 a seguir apresenta
um resumo dos principais tipos numricos implementados em GHC, onde se verifica que so bem
maiores, em termos de quantidade, que os oferecidos pela grande maioria das outras linguagens
de programao.
66

Tabela 3.5: Principais tipos numricos em Haskell.


Tipo
Double
Float
Int
Int8
Int16
Int32
Int64
Integer
Rational
Word
Word8
Word16
Word32
Word64

Descrio
Ponto flutuante de preciso dupla
Ponto flutuante de preciso simples
Inteiro sinalizado de preciso fixa (229 ..229 1)
Inteiro sinalizado de 8 bits
Inteiro sinalizado de 16 bits
Inteiro sinalizado de 32 bits
Inteiro sinalizado de 64 bits
Inteiro sinalizado de preciso arbitrria
Nmeros racionais de preciso arbitrria
Inteiro no sinalizado de preciso fixa
Inteiro no sinalizado de 8 bits
Inteiro no sinalizado de 16 bits
Inteiro no sinalizado de 32 bits
Inteiro no sinalizado de 64 bits

O tipo booleano (Bool)


Os nicos valores booleanos so True e False e sobre eles podem ser utilizadas funes
pr-definidas ou funes construdas pelo usurio. As funes pr-definidas esto mostradas na
Tabela 3.6, a seguir.
Tabela 3.6: Funes booleanas pr-definidas em Haskell.
Funo
&&
||
not

Nome
and
or
inversor

Tipo
&& :: Bool > Bool > Bool
|| :: Bool > Bool > Bool
not :: Bool > Bool

A funo OU exclusivo pode ser definida pelo usurio da seguinte forma:


exOr :: Bool -> Bool -> Bool
exOr True x = not x
exOr False x = x
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 numEquallMax 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
\n - nova linha
\\ - uma barra invertida

\ - aspas simples
\ - aspas duplas
\34 - ?

Existem algumas funes pr-definidas em Haskell feitas para converter caracteres em nmeros
e vice-versa.
toEnum
:: Int -> Char
fromEnum :: Char -> Int
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 peculiar 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,
putStr "\99a\116" = cat
putStr "Dunga\teh o bicho" = Dunga
eh o bicho
putStr "jeri"++ "coa" ++ "coara" = jericoacoara
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,
show (5+7) = "12"
show (True && False) = "False"
read "True" = True
read "14" = 14
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.

3.4.2

Metodologias de programao

Duas metodologias de construo de programas, bastante difundidas, so a programao topdown e a programao bottom-up, de plena aceitao pelos Engenheiros de Software. Haskell
permite que estas duas tcnicas sejam utilizadas em programas nela codificados. Elas sero
mostradas a seguir.
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.
Tabela 3.7: Quantidade
Semana
0
1
2
Total
Mdia

de vendas por semana.


Vendas
12
14
15
41
13.6667

Podemos construir uma funo, imprimeTab, a partir da concatenao de outras funes


que constroem strings e sero desenvolvidas a seguir.
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 imprimeSemanas deve mostrar no monitor o nmero de cada semana e a quantidade de vendas correspondente. Ela ser definida em funo de uma outra funo, imprimeSemana, e so definidas da seguinte forma:
imprimeSemanas :: Int -> String
imprimeSemanas 0 = imprimeSemana 0
imprimeSemanas n = imprimeSemanas (n-1) ++ imprimeSemana n
imprimeSemana :: Int -> String
imprimeSemana n = "|| "++ (show n)++"

| "++ show (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"
A funo imprimeMedia deixada como exerccio para o leitor.
A programao top-down tambm conhecida dividir para conquistar", uma estratgia de
guerra utilizada pelos antigos conquistadores. Como tcnica de programao, consiste na diviso de um problema, inicialmente grande, em sub-problemas menores e mais fceis de serem
resolvidos. Esta tcnica envolve trs fases, descritas da seguinte forma:
Fase de diviso. Esta fase consiste na diviso do problema em sub-problemas menores.
Fase de soluo. Esta fase consiste na soluo de cada sub-problema separadamente. Se
o sub-problema ainda for grande, deve ser usada a mesma tcnica de diviso, de forma
recursiva.
Fase de combinao. Esta fase consiste na combinao das solues dos sub-problemas
em uma soluo nica.
Programao bottom-up
A tcnica de programao bottom-up conhecida como programao dinmica e sua motivao
foi resolver um problema potencial que pode surgir ao se utilizar a tcnica de programao
top-down. Este problema diz respeito possibilidade da gerao de um grande nmero de subproblemas idnticos. Se estes sub-problemas forem resolvidos separadamente, pode levar a uma
replicao muito grande de trabalho. A tcnica bottom-up consiste em resolver, inicialmente, as
menores instncias e utilizar estes resultados intermedirios para resolver instncias maiores.
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:
fib
fib
fib
fib

:: Int -> Int


0 = 1
1 = 1
n = fib (n-1) + fib (n-2)

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 2

fib 3

fib 2

fib 1

fib 1

fib 1

fib 0

fib 0

Figura 3.3: A execuo de fib 4 usando uma definio top-down.


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

Figura 3.4: A execuo de fib 4 usando uma definio bottom-up.


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".
imprimeespacos :: Int -> String
imprimeespacos 0 = ""
71

imprimeespacos n = " "++ imprimeespacos n-1


rJustify :: Int -> String -> String
rJustify n nome
|n > length nome = imprimeespacos (n - length nome) ++ nome
|n == length nome = nome
|otherwise
= "Erro: a string nao cabe no intervalo dado"
imprimeSemana :: Int -> String
imprimeSemana n = rJustify offset (show n) ++
rJustify offset (show (vendas n)) ++ "\n"
where offset :: Int
offset = 10
imprimeMedia :: Int -> String
imprimeMedia n = "| Media | " ++ rJustify 7 (show mediadeVendas n)++" |"
A funo mediadeVendas continua sendo deixada como exerccio para o leitor.
Exerccios:
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.
2. Refaa o exerccio anterior adimitindo a possibilidade de entradas negativas e de que o
segundo argumento seja menor que o primeiro.

3.4.3

Os tipos de dados estruturados de Haskell

Haskell tambm admite a possibilidade de que o usurio construa seus prprios tipos de dados, 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 tipo produto cartesiano
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,
type Pessoa = (String, String, Int)
maria :: Pessoa
maria = ("Maria das Dores", "3225-0000", 22)
intP :: (Int, Int)
intP = (35, 45)
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
Os padres podem ter constantes e/ou padres aninhados.
shift :: ((Int, Int), Int) -> (Int, (Int, Int))
shift ((a,b),c) = (a, (b,c))
Podem ser definidas funes para mostrar casos particulares de uma tupla:
nome :: Pessoa -> String
fone :: Pessoa -> String
idade :: Pessoa -> Int
nome (n, p, a) = n
fone (n, p, a) = p
idade (n, p, a) = a
Assim, nome maria = Maria das Dores"
Deve-se ter algum cuidado com os tipos de dados que, em algumas situaes, podem conduzir
a erros. Por exemplo, so diferentes:
somaPar :: (Int, Int) -> Int
somaPar (a, b) = a + b

somaDois :: Int -> Int -> Int


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.
ehImpar, ehPar :: Int -> Bool
ehImpar 0 = False
ehImpar n = ehPar (n-1)
ehPar 0 = True
ehPar n = ehImpar (n-1)
Definies locais
Haskell permite definies locais atravs da palavra reservada where. Por exemplo,
somaQuadrados :: Int -> Int -> Int
somaQuadrados n m = quadN + quadM
where
quadN = n * n
quadM = m * m
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
quady
quad
quad z

= quad x
= quad y
:: Int -> Int
= 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,
maximasOcorrencias :: Int -> Int -> Int -> (Int, Int)
maximasOcorrencias n m p = (max, quantosIguais)
where
max
= maximoDeTres n m p
quantosIguais
= quantosIguaisValor
maximoDeTres
:: Int -> Int -> Int
maximoDeTres a b c = maximo (maximo (a,
quantosIguaisValor :: Int -> Int -> Int

max n m p
-> Int
b), c)
-> Int -> Int

onde a funo quantosIguaisValor pode ser definida de uma das formas mostradas na Tabela
3.8.
Tabela 3.8: Formas possveis de definio da funo quantosIguaisValor.
quantosIguaisValor valor n m p
= ehN + ehM + ehP
where
ehN = if n == valor then 1 else 0
ehM = if m == valor then 1 else 0
ehP = if p == valor then 1 else 0

3.4.5

quantosIguaisValor valor n m p
= ehvalor n + ehvalor m + ehvalor p
where
ehvalor :: Int > Int
ehvalor x = if x == valor then 1 else 0

Expresses condicionais

Como pode ser observado na definio da funo quantosIguaisValor, a expresso condicional


do tipo if-then-else foi utilizada como uma expresso vlida em Haskell. Neste caso, ao usar a
construo if <expB> then <exp1> else <exp2>, a expresso booleana <expB> avaliada
e, se o resultado for verdadeiro, a expresso <exp1> escolhida para ser avaliada e seu resultado
ser o valor da expresso como um todo. Se, ao contrrio, a avaliao da expresso booleana
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.
somaQuadrados 4 3 = quadN + quadM
where
quadN = 4 * 4 = 16
quadM = 3 * 3 = 9
= 16 + 9
= 25
maximasOcorrencias 2 1 2 = (max, quantosIguais)
where
max = maximoDeTres 2 1 2
= maximo (maximo 2 1) 2
?? 2>=1 = True
= maximo 2 2
?? 2>=2 = True
= 2
= (2, quantosIguais)
where
quantosIguais
= quantosIguaisValor 2 2 1 2
= ehValor 2 + ehvalor 1 + ehvalor 2
where
ehvalor 2 = if 2 == 2 then 1 else 0
= if True then 1 else 0
= 1
= 1 + ehvalor 1 + ehvalor 2
where
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:
1. Calcule os valores das expresses: maximasOcorrencias 1 2 1 e quantosIguaisValor
4 2 1 3.
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?
Exemplo. Vamos agora construir um exemplo bastante conhecido que o de encontrar as
razes reais de uma equao do segundo grau com coeficientes reais, baseado em [46]. Neste caso
teremos como entrada a equao a x2 + b x + c = 0, sendo a = 1.0, b = 5.0 e c = 6.0.
Para esta soluo, a sada ser a string
A equao 1.0 * x 2 + 5.0 * x + 6.0 = 0.0
tem duas razes reais e distintas: -2.0 e -3.0.
Para isto vamos construir duas funes: umaRaiz para o caso da funo ter duas razes reais
e iguais e duasRaizes para o caso dela ter duas razes reais e distintas.
umaRaiz :: Float -> Float -> Float -> Float
umaRaiz a b c = -b / (2.0 * a)
duasRaizes :: Float -> Float -> Float -> (Float, Float)
duasRaizes a b c = (d + e, d - e)
where
d = -b/(2.0*a)
e = sqrt (b^2 - 4.0*a*c)/(2.0*a)
saida :: Float -> Float -> Float -> String
saida a b c = cabecalho a b c ++ raizes a b c
cabecalho :: Float -> Float -> Float -> String
cabecalho a b c = "A equacao \n\n\t"++ show a ++ "*x^2 + " ++
76

show b ++ "*x + " ++ show c ++ " = 0.0" ++ "\n\ntem "


raizes :: Float -> Float -> Float -> String
raizes a b c
| b^2 > 4.0 * a * c = "duas raizes reais e distintas: "
++ show f ++ " e " ++ show s
|b^2 == 4.0 * a * c = "duas raizes reais e iguais: "
++ show (umaRaiz a b c)
|otherwise = "nenhuma raiz real "
where (f, s) = duasRaizes a b c
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"
A redefinio da funo duasRaizes deixada para o leitor, como exerccio.

3.5

Projeto de programas

A Engenharia de Software admite algumas metodologias para a construo de programas, de


forma a obter melhores resultados, tanto em relao s solues quanto em relao ao tempo de
desenvolvimento. A seqncia de passos mostrada a seguir, devida a Simon Thompson [46],
considerada um bom roteiro na codificao de programas para a soluo de problemas usando
Haskell:
Verificar problemas similares e mais simples.
Decidir os tipos para representao.
Dividir para conquistar (abordagem top-down).
Uma soluo para um caso menor pode ser utilizada para um caso maior.
O uso de clusulas where.
Se uma expresso aparecer mais de uma vez, forte candidata a ser declarada como uma
funo.
Usar uma abordagem bottom-up.
O layout do script importante.
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).
1. Defina funes que retornem a ordenada e a abcissa de um 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.
4. Dados dois vetores, u e v, determine se eles so, ou no, colineares.

3.5.1

Provas de programas

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:
troca :: (Int, Int) -> (Int, Int)
troca (a, b) = (b, a)

--def 1

cicla, recicla :: (Int, Int, Int) -> (Int, Int, Int)


cicla (a, b, c) = (b, c, a)
--def 2
recicla (a, b, c) = (c, a, b)
--def 3
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:
troca (troca (a, b))

= troca (b, a)
= (a, b)

--por def 1
--por def 1

cicla (recicla (a, b, c)) = cicla (c, a, b) = (a, b, c)


--por def 3 e 2
recicla(cicla (a, b, c)) = recicla (b, c, a) = (a, b, c) --por def 2 e 3
Portanto, so iguais os resultados e a prova est completa. Fcil, no?
78

Provas por casos


Seja a definio da funo maximo, j feita anteriormente no incio do Captulo:
maximo :: Int -> Int -> Int
maximo n m
|n >= m = n
--def 1
|otherwise = m
--def 2
Seja a assertiva: Para quaisquer nmeros inteiros n e m, maximo n m n.
Para quaisquer nmeros m e n definidos, tem-se: m > n ou n m. Ento,
Caso 1: se n m: maximo n m = n (def 1) e n n. Portanto, maximo n m n.
Caso 2: se m > n: maximo n m = m (def 2) e m > n. Portanto, maximo n m > n. Logo,
maximo n m n.
Exerccios
1. Prove que cicla (cicla (cicla (a, b, c))) = (a, b, c) para todo a, b e c.
2. Sendo somaTres (a, b, c) = a + b + c, para a, b e c inteiros, d uma prova de que
somaTres (cicla (a, b, c)) = somaTres (a, b, 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 programas codificados em Haskell.
Para provar que uma propriedade P(n) vlida para todo natural n, deve-se:
Caso base: Provar P(n), para n = 0.
Passo indutivo: Para n > 0, provar P(n), assumindo que P(n-1) vlida.
Vejamos, por exemplo, a funo fatorial:
fatorial :: Int -$>$ Int
fatorial 0 = 1
fatorial n = n * fatorial (n - 1)

-- (fat 1)
-- (fat 2)

Podemos provar a seguinte propriedade dos naturais:


P(n): fatorial n > 0, para todo natural n.
O esquema de prova feito da seguinte forma:
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.
Esta ltima parte, a concluso da prova, um componente importante da prova. comum
ver esquemas de prova, normalmente feitas por iniciantes, onde os dois passos so verificados,
mas no existe a concluso. Neste caso, a prova est incompleta.

Provas por Induo


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:
Estgio 1:
Estgio 2:

Estgio 3:
Estgio 4:

escrever o objeto da prova em portugus,


escrever o objeto da prova em linguagem formal,
escrever os sub-objetos da prova por induo:
P(0):
P(n), para todo n>0, assumindo P(n-1)
Provar P(0)
Provar P(n), para n>0, lembrando que deve e pode usar P(n-1)

Exemplo: Sejam as definies das funes a seguir:


power2 :: Int -> Int
power2 0 = 1
power2 r = 2 * power2 (r - 1)

(1)
(2)

sumPowers :: Int -> Int


sumPowers 0 = 1
sumPowers r = sumPowers (r-1) + power2 r

(3)
(4)

Prove que sumPowers n + 1 = power2 (n + 1).


80

Estgio 0:
Estgio 1:
Estgio 2:

Estgio 3:

provar que a soma das potncias de 2 de 0 a n, adicionada a 1


igual a (n + 1)-sima potncia de 2.
provar P(n): sumPowers n + 1 = power2 (n+1)
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
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.
sumPowers n + 1 = sumPowers (n-1) + power2 n + 1
por (4)
= sumPowers(n-1) + 1 + power2 n
pela comutatividade de +
= power2 n + power2 n
pela hip. de induo
= 2 * power2 n

Concluso: como a propriedade vlida para n = 0 e para n > 0, ento vlida para todo
nmero natural n.
Exerccios
1. Prove que, para todo nmero natural n, fatorial (n + 1) power2 n.
2. Prove que, para todo nmero natural n, fib (n+1) power2 (n div 2).
3. D uma prova de que, para todo nmero natural n, venda n 0.

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
Commputers specially designed for applicative languages
implement recursive functions very efficiently.
Also, architectural features can be included in conventional
computers that significantly increase
the speed of recursive-function invocations.
(Bruce J. MacLennan in [25])

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.
Algumas caractersticas importantes das listas em Haskell, so:
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 [m..n], para m>n, vazia. Por exemplo, [7 .. 3] = [ ].
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.

4.2

Funes sobre listas

As funes para a manipulao de listas so declaradas da mesma forma como so declaradas


para processar outros tipos de dados, usando casamento de padres. Neste caso, os padres so
apenas dois: a lista vazia e a lista no vazia. Por exemplo,
somaLista :: [Int] -> Int
somaLista [ ] = 0
somaLista (a:x) = a + somaLista x
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].
somaLista [2,3,5,7] =2 + somaLista [3,5,7]
=2 + 3 + somaLista [5,7])
=5 + somaLista [5,7]
=5 + 5 + somaLista [7]
=10 + somaLista [7]
=10 + 7 + somaLista [ ]
=17 + somaLista [ ]
=17 + 0
=17
O construtor de listas : (cons)
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:
(:) :: Int -> [Int] -> [Int]
No entanto, verificamos tambm que True : [False] = [True, False]. Agora cons tem o
tipo:
(:) :: Bool -> [Bool] -> [Bool]
Isto mostra que o operador cons polimrfico. Desta forma, seu tipo :
(:) :: t -> [t] -> [t]
onde [t] uma lista de valores, de qualquer tipo, desde que seja homognea.
Construindo funes sobre listas
Em Haskell, j existe um grande nmero de funes pr-definidas para a manipulao de listas.
Estas funes fazem parte do arquivo Prelude.hs, carregado no momento em que o sistema
chamado e permanece ativo at o final da execuo. Um resumo destas funes, com seus tipos
e exemplos de utilizao, pode ser visto na Tabela 4.1.
Tabela 4.1: Algumas funes polimrficas do Prelude.hs.
Funo
Tipo
Exemplo
:
a > [a] > [a]
3:[2,5]=[3,2,5]
++
[a] > [a] > [a]
[3,2]++[4,5]=[3,2,4,5]
!!
[a] > Int > a
[3,2,1]!!0=3
concat
[[a]] > [a]
[[2],[3,5]]=[2,3,5]
length
[a] > Int
length [3,2,1]=3
head
[a] > a
head [3,2,5]=3
last
[a] > a
last [3,2,1]=1
tail
[a] > [a]
tail [3,2,1]=[2,1]
init
[a] > [a]
init [3,2,1]=[3,2]
replicate Int > a > [a]
replicate 3 a=[a,a,a]
take
Int > [a] > [a]
take 2 [3,2,1]=[3,2]
drop
Int > [a] > [a]
drop 2 [3,2,1]=[1]
splitAt
Int > [a] > ([a], [a]) splitAt 2 [3,2,1]=([3,2],[1])
reverse
[a] > [a]
reverse [3,2,1]=[1,2,3]
zip
[a] > [b] > [(a, b)]
zip[3,2,1][5,6]=[(3,5),(2,6)]
unzip
[(a, b)] > ([a], [b])
unzip [(3,5),(2,6)]=([3,2],[5,6])
and
[Bool] > Bool
and [True,False]=False
or
[Bool] > Bool
or [True,False]=True
sum
[Int] > Int
sum [2,5,7]=14
[F loat] > F loat
sum [3.0,4.0,1.0]=8.0
product [Int] > Int
product [1,2,3]=6
[F loat] > F loat
product [1.0,2.0,3.0]=6.0

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:
pertence :: Int -> [Int] -> Bool
pertence b [ ]
= False
pertence b (a:x) = (b == a) || pertence b x
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 espelhaV, 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.
cavalo
.......##...
.....##..#..
...##.....#.
..#.......#.
..#...#...#.
..#...###.#.
..#...#..##.
..#...#.....
...#...#....
....#..#....
.....#.#....
......##....

espelhaH cavalo
......##....
.....#.#....
....#..#....
...#...#....
..#...#.....
..#...#..##.
..#...###.#.
..#...#...#.
..#.......#.
...##.....#.
.....##..#..
.......##...

espelhaV cavalo
...##.......
..#..##.....
.#.....##...
.#.......#..
.#...#...#..
.#.###...#..
.##..#...#..
.....#...#..
....#...#...
....#..#....
....#.#.....
....##......

A declarao do tipo Quadro pode ser feita da seguinte forma:


type Linha = [Char]
type Quadro = [Linha]
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,
espelhaH cav = reverse cav
espelhaV cav = map reverse cav

--inverte os elementos de uma lista

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
1. Dada a definio da funo dobra
dobra :: [Int] -> [Int]
dobra [ ] = [ ]
dobra (a:x) = (2 * a) : dobra x
Calcule dobra [3,4,5] passo a passo.
2. Escreva [False, False, True] e [2] usando : e [ ].
3. Calcule somaLista [30, 2, 1, 0], dobra [0] e cafe++com++ leite.
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.
ordena :: [Int] -> [Int]
ordena [ ] = [ ]
ordena (a:x) = insere a (ordena x)
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.
insere :: Int -> [Int] -> [Int]
insere a [ ] = [a]
insere a (b:y)
|a <= b = a : (b : y)
-- a serah a cabeca da lista
|otherwise = b : insere a y -- procura colocar a no local correto
Este mtodo de ordenao conhecido como insero direta e o leitor deve observar a simplicidade como ele implementado em Haskell. Sugerimos comparar esta implementao com
outra, em qualquer linguagem convencional. Alm disso, tambm se deve considerar a possibilidade 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
1. Mostre todos os passos realizados na chamada ordena [2, 8, 1].
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.
3. D uma definio da funo pertence, vista anteriormente, usando a funo numOcorre,
do tem anterior.
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].

4.3

Pattern matching revisado

O casamento de padres j foi analisado anteriormente, mas sem qualquer profundidade e formalismo. Agora ele ser visto com uma nova roupagem, apesar de usar conceitos j conhecidos.
Os padres em Haskell so dados por:
valores literais como -2, C e True.
variveis como x, num e maria.
o caractere _ (sublinhado) casa com qualquer argumento.
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 .
um construtor aplicado a outros padres. Por exemplo, o construtor de listas (p1 : p2 ),
onde p1 e p2 so padres
Nas linguagens funcionais, a forma usada para verificar se um padro casa, ou no, com um
valor realizada da seguinte maneira:
primeiramente, verifica-se se o argumento est na forma correta e
depois associam-se valores s variveis dos padres.
Exemplo. A lista [2,3,4] casa com o padro (a : x) porque:
1. ela tem cabea e tem cauda, portanto uma lista correta. Portanto,
2. 2 associado com a cabea a e [3,4] associada com a cauda x.
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 constante, a casa com p se a == p.
se p for uma varivel, a casa com p e a ser associado com p.
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.
Exemplo. Seja a funo zip definida da seguinte forma:
zip (a:x) (b:y) = (a, b) : zip x y
zip _ _ = [ ]
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.
zip [2,3,4] [4,5,78] = [(2,4), (3,5), (4,78)].
zip [2,3] [1,2,3] = [(2,1),(3,2)]
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.
3. Calcule somaPar [(2,3), (96, -7)], passo a passo.
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:
type Pessoa = String
type Livro = String
type BancodeDados = [(Pessoa, Livro)]
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")]
Vamos definir funes para realizar as seguintes tarefas:
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:
Uma funo que atualiza o banco, quando um livro emprestado a algum.
Uma funo que atualiza o banco quando um livro devolvido.
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.
livrosEmprestados :: BancodeDados -> Pessoa -> [Livro]
livrosEmprestados [ ] _ = [ ]
livrosEmprestados ((inquilino, titulo) : resto) fulano
| inquilino == fulano = titulo : livrosEmprestados resto fulano
| otherwise = livrosEmprestados resto fulano
90

Vamos agora definir as funes de atualizao:


tomaEmprestado :: BancodeDados -> Pessoa -> Livro -> BancodeDados
tomaEmprestado dBase pessoa titulo = (pessoa, titulo) : dBase
devolveLivro :: BancodeDados -> Pessoa -> Livro -> BancodeDados
devolveLivro ((p, t): r) f l
| p == f && t == l = r
| otherwise = (p,t) : devolveLivro r f l
devolveLivro [ ] ful tit = error "Nao ha livro emprestado"
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.

4.4

Compreenses e expresses ZF (Zermelo-Fraenkel)

As compreenses, tambm conhecidas como expresses ZF, so devidas a Zermelo e Fraenkel e


representam uma forma muito rica de construo de listas. O domnio desta tcnica permite
ao programador resolver muitos problemas de maneira simples e, em muitos casos, inusitada.
A sintaxe das expresses ZF muito prxima da descrio matemtica de conjuntos por intensionalidade, exprimindo determinadas propriedades. As diferenas se verificam apenas nos
sinais utilizados nas representaes, mas a lgica subjacente a mesma. Vamos mostrar estas
semelhanas atravs de exemplos e depois vamos formalizar sua sintaxe.
Vamos supor que ex = [2,4,7]. Usando ex, podemos construir ex1, a lista cujos elementos
sejam o dobro dos elementos de ex, da seguinte forma:
ex1 = [2*a | a<-ex]
Desta forma ex1 = [4,8,14]. Se quizermos encontrar a lista ex2 composta dos elementos de ex
que sejam pares, podemos declarar
ex2 = [a | a<-ex, a mod 2 == 0]
Neste caso, ex2 = [2,4].
A partir destes exemplos, podemos verificar que a sintaxe das expresses ZF realmente
simples. Formalmente ela dada da seguinte forma:
[ e | q1 , ..., qk ] onde cada qi um qualificador, que pode ter uma das seguintes formas:
1. um gerador do tipo p< lExp, onde p um padro e lExp uma lista, ou
2. pode ser um teste do tipo bExp, uma expresso booleana.
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
[2*a | a <- ex, a mod 2 == 0, a > 3] = [8]
Pode-se usar qualquer padro esquerda de <
somaPares :: [(Int, Int)] -> [Int]
somaPares listadePares = [a + b | (a, b) <- listadePares]
somaPares [(2,3), (4,5), (6,7)] = [5,9,13]
Pode-se adicionar testes
novaSomaPares :: [(Int, Int)] -> [Int]
novaSomaPares listadePares = [a + b | (a, b) <- listadePares, a < b]
novaSomaPares [(2,3), (5,4), (7,6)] = [5]
possvel colocar mltiplos geradores e combinar geradores e testes.
Uma expresso lExp ou bExp que aparece em um qualificador qi pode referenciar variveis
usadas nos padres dos qualificadores q1 at qi1.
O algoritmo quicksort
Na seo 4.1 foi mostrado como o algoritmo de ordenao por insero direta pode ser implementado 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 sublistas: 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.


quicksort :: [t] -> [t]
quicksort [ ] = [ ]
quicksort (a : x) = quicksort [y | y <- x, y <= a] ++ [a] ++
quicksort [y | y <- x, y > a]
Esta definio pode tambm ser feita usando definies locais, tornando-a mais fcil de ser
compreendida, da seguinte forma.
quicksort :: [t] -> [t]
quicksort [ ] = [ ]
quicksort (a : x) = quicksort menores ++ [a] ++ quicksort maiores
where menores = [y | y <- x, y <= a]
maiores = [y | y <- x, y > a]
No fantstica esta definio? Sugiro ao leitor verificar a implementao deste algoritmo
utilizando alguma linguagem imperativa como C, C++ ou Java, observando as diferenas em
facilidade de entendimento e de implementao.
Mais exemplos.
1. A funo fazpares:
fazpares :: [t] -> [u] -> [(t,u)]
fazpares l m = [ (a, b) | a <- l, b <- m]
fazpares [1,2,3] [4,5] = [(1,4), (1,5), (2,4), (2,5), (3,4), (3,5)]
2. A funo pares:
pares :: Int -> [(Int, Int)]
pares n = [ (a, b) | a <- [1 .. n], b <- [1 .. a]]
pares 3 = [ (1,1), (2,1), (2,2), (3,1), (3,2), (3,3)]
3. Os tringulos retngulos de lados menores ou iguais a um certo valor inteiro:
trianguloretangulo :: Int -> [(Int, Int, Int)]
trianguloretangulo n = [ (a, b, c) | a <- [2 .. n], b <- [a+1 .. n],
c <- [b+1 .. n], a * a + b * b == c * c]
trianguloretangulo 100 = [(3,4,5), (5,12,13), (6,8,10), ..., (65,72,97)]
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:
livrosEmprestados :: BancodeDados -> Pessoa -> [Livro]
livrosEmprestados db fulano = [liv | (pes, liv) <- db, pes == fulano]
Exerccios:
1. Re-implemente as funes de atualizao do Banco de Dados para a Biblioteca, feitas na
seo 4.2, usando compreenso de listas, em vez de recurso explcita.
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
Maria
gostava de bananas e
estava apaixonada
por
Joaquim e
tomou
veneno para
morrer.
deve ser transformado em um texto mais organizado, ficando da seguinte forma:
Maria gostava de bananas e estava apaixonada
por Joaquim e tomou veneno para morrer.
Para isto, vamos construir algumas funes para realizar tarefas auxiliares. Inicialmente,
devemos observar que uma palavra uma sequncia de caracteres que no tem espaos em
branco dentro dela. Os espaos em branco so definidos da seguinte forma:
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:
pegaPalavra :: String -> String
pegaPalavra [ ] = [ ]
pegaPalavra (a:x)
|pertence a espacoEmBranco
= [ ]
|otherwise
= a : pegaPalavra x
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".
tiraPalavra :: String -> String
tiraPalavra [ ] = [ ]
tiraPalavra (a : x)
|pertence a espacoEmBranco = (a : x)
|otherwise
= tiraPalavra x
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.
tiraEspacos :: String -> String
tiraEspacos [ ] = [ ]
tiraEspacos (a : x)
|pertence a espacoEmBranco = tiraEspacos x
|otherwise
= (a : x)
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).
type Palavra = String
divideEmPalavras :: String -> [Palavra]
divideEmPalavras st = divide (tiraEspacos st)
divide :: String -> [Palavra]
divide [ ] = [ ]
divide st = (pegaPalavra st) : divide (tiraEspacos (tiraPalavra st))
Vamos acompanhar a seqncia de operaes da aplicao divideEmPalavras "bicho
bom".
divideEmPalavras " bicho bom"
= divide (tiraEspacos " bicho bom")
= divide "bicho bom"
= (pegaPalavra "bicho bom") : divide (tiraEspacos (tiraPalavra "bicho bom"))
= "bicho": divide (tiraEspaco " bom")
= "bicho" : divide "bom"
= "bicho" : (pegaPalavra "bom") : divide (tiraEspacos (tiraPalavra "bom"))
= "bicho" : "bom" : divide (tiraEspacos [ ])
= "bicho" : "bom" : divide [ ]
= "bicho" : "bom" : [ ]
= ["bicho", "bom"]
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.
type Linha = [Palavra]
formaLinha :: Int -> [Palavra] -> Linha
Inicialmente, vamos admitir algumas premissas:
Se a lista de palavras for vazia, a linha tambm ser vazia.
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).
Se a primeira palavra no se ajustar, a linha ser vazia.
formaLinha tam [ ] = [ ]
formaLinha tam (p:ps)
|length p <= tam = p : restoDaLinha
|otherwise
= [ ]
where
novoTam
= tam - (length p + 1)
restoDaLinha = formaLinha novoTam ps
Vamos acompanhar a seqncia de aplicao da funo:
formaLinha 20 ["Maria", "foi", "tomar", "banho", ...
= "Maria" : formaLinha 14 ["foi", "tomar", "banho", ...
= "Maria" : "foi": formaLinha 10 ["tomar", "banho" ...
= "Maria" : "foi": "tomar" : formaLinha 4 ["banho", ...
= "Maria" : "foi": "tomar" : [ ]
= ["Maria","foi","tomar"]
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.
tiraLinha :: Int -> [Palavra] -> [Palavra]
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.
divideLinhas :: Int -> [Palavra] -> [Linha]
divideLinhas _ [ ]
= [ ]
divideLinhas tamLin x =
formaLinha tamLin x : divideLinhas (tiraLinha tamLin x)
96

Falta agora construir uma funo que transforme uma string em uma lista de linhas, formando
o novo texto identado esquerda.
preenche :: Int -> String -> [Linha]
preenche tam st = divideLinhas tam (divideEmPalavras st)
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.
juntaLinhas :: [Linha] -> String
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, adicionando 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).

4.5

Funes de alta ordem

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)
Como outro exemplo, vamos considerar a seguinte definio em Haskell:
let suc
= soma 1
soma x = somax
where somax y = x + y
in suc 3
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.
As funes de alta ordem so usadas de forma intensa em programas funcionais, permitindo
que computaes complexas sejam expressas de forma simples. Vejamos algumas destas funes,
muito utilizadas na prtica da programao funcional.

4.5.1

A funo map

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
listaTupla ["Dunga","Constantino"] = [("Dunga",5),("Constantino",11)]
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.
tuplaNum :: [Char] -> ([Char], Int)
tuplaNum s = (s, length s)
Agora a funo listaTupla pode ser definida da seguinte maneira:
listaTupla :: [String] -> [(String, Int)]
listaTupla [ ]
=
[ ]
listaTupla (s : xs) = (tuplaNum s) : listaTupla xs
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,
dobraLista [3, 2, 5] = [6, 4, 10]
Como na definio da funo anterior, vamos construir a funo auxiliar, dobra, da seguinte
forma:
98

dobra :: Int -> Int


dobra n = 2*n
dobraLista :: [Int] -> [Int]
dobraLista [ ]
= [ ]
dobraLista (n : x) = (dobra n) : dobraLista x
Analisando as definies listaTupla e dobraLista, observamos a presena de um padro que
o de aplicar uma mesma funo a cada elemento da lista, ou seja, as duas funes percorrem
as listas aplicando uma funo a cada um de seus elementos.
Uma outra opo construir uma funo de alta ordem, destinada a realizar esta varredura,
aplicando a mesma funo a cada elemento da lista. A funo a ser aplicada passada como
parmetro para a funo de alta ordem. Neste caso, a funo de alta ordem chamada de
mapeamento, simbolizado pela funo pr-definida map, definida em Haskell da seguinte forma:
map :: (t -> u) -> [t] -> [u]
map f [ ]
= [ ]
map f (a : x) = (f a) : (map f x)
Assim, as definies anteriores de listaTupla e dobraLista podem ser re-definidas da
seguinte forma:
listaTupla :: [String] -> [(String, Int)]
listaTupla xs = map tuplaNum xs
dobraLista :: [Int] -> [Int]
dobraLista x = map dobra x
Vejamos mais alguns exemplos:
duplica, triplica :: Int -> Int
duplica n = 2 * n
triplica n = 3 * n
duplicaLista, triplicaLista :: [Int] -> [Int]
duplicaLista l = map duplica l
triplicaLista l = map triplica l
map duplica [4,5]

=
=
=
=
=
=
=

duplica 4 : map duplica [5]


8 : map duplica [5]
8 : (duplica 5 : map duplica [ ])
8 : (10 : map duplica [ ])
8 : (10 : [ ])
8 : [10]
[8,10]

a utilizao de funes de alta ordem se justifica, tendo em vista as seguintes premissas:


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.


Exemplo: as funes de anlise de vendas, mostradas no Captulo anterior, foram definidas
para analisar uma funo fixa: vendas. Agora, a funo totalVendas pode ser dada por
totalVendas n = somaLista (map venda [0..n])
Muitas outras funes podem ser definidas usando map. Por exemplo,
somaQuad :: Int -> Int
somaQuad n = somaLista (map quad [1..n])
quad :: Int -> Int
quad n = n * n
onde a funo somaLista foi definida no incio deste Captulo.
Exerccios
1. D definies de funes que tome uma lista de inteiros l e
retorne a lista dos quadrados dos elementos de l,
retorne a soma dos quadrados dos elementos de l e
verifique se todos os elementos da lista so positivos.
2. Defina funes que
d o valor mnimo de uma funo aplicada a uma lista de 0 a n,
teste se os valores de f sobre as entradas 0 a n so todas iguais.
teste se todos os valores de f aplicada s entradas de 0 a n so maiores ou iguais a
zero e
teste se os valores f 0, f 1 at f n esto em ordem crescente.
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 .

4.5.2

Funes annimas

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
Pode-se observar que quando se necessita especificar um valor, normalmente, se declara um
identificador para isto. Esta tambm tem sido a forma utilizada com as funes, ou seja, declarase uma funo com um nome e, quando a funo for necessria, ela referenciada pelo seu nome.
Uma alternativa, possvel em Haskell, consiste em declarar uma funo apenas no ponto de
chamada, sem ligar esta funo a algum identificador. Estas funes so chamadas de funes
annimas e se baseiam na notao do -clculo, visto no Captulo 2. Esta forma de definio
de funes mais compacta e mais eficiente. A funo somaNum, definida acima usando a
clusula where, pode ser escrita, de forma annima, da seguinte forma:
\m -> n + m
As funes dobraLista e listaTupla podem ser definidas da seguinte forma:
dobraLista l = map (\n -> 2*n) l
listaTupla ls = map (\s -> (s, length s)) ls
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:
antes da flexa vm os argumentos e
depois da flexa vem o resultado.
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).

fx
g

g (f x) (f y)

fy

Figura 4.1: Forma grfica da funo comp2.


A definio de comp2
comp2 :: (a -> b) -> (b -> b -> c) -> (a -> a -> -c)
comp2 f g = (\x y -> g (f x) (f y))
Para se adicionar os quadrados de 5 e 6, podemos escrever
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
ento f pode ser definida anonimamente por
\x y z -> resultado

As funes fold e foldr


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.
fold :: (t -> t -> t) -> [t] -> t
fold f [a]
= a
fold f (a:b:x) = f a (fold f (b:x))
Exemplos:
fold (||) [False, True, False]

fold (++) ["Chico", "Afonso", , "!"]


fold (*) [1..6]

=
=
=
=
=
=
=

(||) False (fold (||) [True, False])


(||) False ((||) True (fold (||) [False])
(||) False ((||) True False)
(||) False True
True
"Chico Afonso!"
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:
foldr :: (t -> u -> u) -> u -> [t] -> u
foldr f s [ ]
= s
foldr f s (a : x) = f a (foldr f s x)
Vamos verificar como algumas funes so definidas usando foldr.
Exemplos:
concat :: [[t]] -> [t]
concat xs = foldr (++) [ ] xs

and :: [Bool] -> Bool


and bs = foldr (&&) True bs

rev :: [t] -> [t]


rev l = foldr stick [] l

stick :: t -> [t] -> [t]


stick a x = x ++ [a]
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:
1. filter ehPar [2,3,4] = [2,4].
2. Um nmero natural perfeito se a soma de seus divisores, incluindo o nmero 1, for o
prprio nmero. Por exemplo, 6 o primeiro nmero natural perfeito, porque 6 = 1+2+3.
Vamos definir uma funo que mostra os nmeros perfeitos entre 0 e m.
divide :: Int -> Int -> Bool
divide n a = n mod a == 0
fatores :: Int -> [Int]
fatores n = filter (divide n) [1..(n div 2)]
perfeito :: Int -> Bool
perfeito n = sum (fatores n) == n
perfeitos m = filter perfeito [0..m]
E se quizermos os primeiros m nmeros perfeitos?
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. Selecionando elementos: filter digits "18 Maro 1958"= "181958"
A definio formal de filter :
filter :: (t -> Bool) -> [t] -> [t]
filter p [ ] = [ ]
filter p (a : x)
ou
|p a
= a : filter p x
|otherwise = filter p x

4.6

filter p l = [a | a <- l, p a]

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, proporcionada pelo aumento da reusabilidade.
Polimorfismo a capacidade de aplicar uma mesma funo a vrios tipos de dados, representados 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
quadrado :: Int -> Int
quadrado x = x * x
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.

4.6.1

Tipos variveis

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:
length :: [t] -> Int
(++) :: [t] -> [t] -> [t]
rev :: [t] -> [t]
id :: t -> t
zip :: [t] -> [u] -> [(t, u)]

4.6.2

O tipo mais geral

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.
rep :: Int -> t -> [t]
rep 0 ch = [ ]
rep n ch = ch : rep (n - 1) ch
fst :: (t, u) -> t

snd :: (t, u) -> u


104

fst (x, _) = x

snd (_, y) = y

head :: [t] -> t


head (a : _) = a

tail :: [t] -> [t]


tail (_ : x) = x

Mas qual a vantagem de se ter polimorfismo? A resposta vem da Engenharia de Software e


se baseia nos seguintes fatos:
definies mais gerais implicam em maior chance de reutilizao e
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.

4.7

Induo estrutural

J vimos formas de se provar propriedades em Haskell. No entanto, quando estas propriedades


envolvem listas, existe uma forma especfica de serem provadas, que a induo estrutural. Podese dizer que induo estrutural o mtodo de induo matemtica aplicado s listas finitas. As
listas infinitas no so tratadas, uma vez que elas no so estruturas modelveis na Computao.
Os computadores so mquinas com memrias limitadas, apesar de poderem ser grandes, mas
so finitas. Apesar de alguns autores se referirem s listas infinitas, o que realmente eles se
referem so as listas potencialmente infinitas, que so implementadas em Haskell atravs de
um mecanismo de avaliao lazy, um tpico a ser visto mais adiante. Deve ser lembrado que a
lista vazia, [ ], uma lista finita e a lista no vazia, (a:x), uma lista finita, se a lista x for
finita.
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:
Estgio 1:
Estgio 2:

Estgio 3:
Estgio 4:

escrever o objetivo da prova informalmente,


escrever o objetivo da prova formalmente,
escrever os sub-objetivos da prova por induo:
P([ ]) e
P(a : x), assumindo P(x)
provar P([ ])
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 estgios enumerados anteriormente.
Exemplo 1. Dadas as definies a seguir:
somaLista [ ]
= 0
somaLista (a : x) = a + somaLista x

(1)
(2)

dobra [ ]
= [ ]
dobra (a : x) = (2 * a) : dobra x

(3)
(4)

Provar que 2*(somaLista l) = somaLista (dobra l)


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))


assumindo que 2 * somaLista x = somaLista (dobra x)

(7)
(8)

Estgio 3: Caso base:


lado esquerdo do caso base:
2 * somaLista [ ]
=2*0
=0

por (1)
pela aritmtica

lado direito do caso base


somaLista (dobra [ ]
= somaLista [ ]
=0

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)
= 2 * a + somaLista (dobra x)
= 2 * a + 2 * somaLista x
= 2 * (a + somaLista x)

por (4)
por (2)
pela hiptese de induo
pela distributividade de *.
106

por (3)
por (1)

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.
Estgio 0: a funo ++ associativa.
Estgio 1: Dadas as definies:
[ ] ++ v = v
(1)
(a : x) ++ v = a : (x ++ v)

(2)

x ++ (y ++ z) = (x ++ y) ++ z
Estgio 2:
caso base: [ ] ++ (y ++ z) = ([ ] ++ y) ++ z (3)
passo indutivo: (a : x) ++ (y ++ z) = ((a : x) ++ y) ++ z (4) assumindo
que x ++ (y ++ z) = (x ++ y) ++ z (5)
Estgio 3:
caso base: lado esquerdo
[ ] ++ (y ++ z)
= y ++ z

por (1)

caso base: lado direito


([ ] ++ y) ++ z
= 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)

passo indutivo: lado direito


((a : x) ++ y) ++ z
= (a : (x ++ y)) ++ z
= a : ((x ++ y) ++ z)
= a : (x ++ (y ++ z))

por (2)
por (2)
pela hiptese de induo.

Como o lado esquerdo e o lado direito do passo indutivo so iguais, ento a propriedade
vlida para ele.
Concluso: como a assertiva vlida para o caso base e para o passo indutivo, ento ela
verdadeira para todas as listas finitas.
Exerccios
1. Prove que, para todas as listas finitas x, x ++ [ ] = x.
2. Tente provar que x ++ (y ++ z) = (x ++ y) ++ z usando induo estrutural sobre
z. H alguma coisa esquisita com esta prova? O qu?
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).
4. Mostre que, para todas as listas finitas x e y,
dobra (x ++ y) = dobra x ++ dobra y e
length (x ++ y) = length x + length y.
5. Prove por induo sobre x que, para todas as listas finitas x,
somaLista (x ++ (a : y)) = a + somaLista (x ++ y).
6. Prove, usando ou no o exerccio anterior, que para todas as listas finitas x,
somaLista (double x) = somaLista (x ++ x).

4.8

Composio de funes

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:
divideEmPalavras :: String -> [Palavra]
divideEmPalavras st = divide (tiraEspacos st)
Esta funo pode ser reescrita como: divideEmPalavras st = divide . tiraEspacos st
Como mais um exemplo, vamos considerar a definio
let quad
quadrado x
sucessor x
compoe (f,g)
in quad 3

=
=
=
=

compoe (quadrado, sucessor)


x * x
x + 1
h where h x = f(g(x))

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
A funo divideEmPalavras, definida anteriormente, pode ser re-definida usando composio avanada, da seguinte forma:
divideEmPalavaras = tiraEspacos > . > divide
Deve-se ter o cuidado de observar que f . g x diferente de (f . g) x porque a aplicao
de funes tem prioridade sobre a composio. Por exemplo, succ . succ 1 resultar em erro
porque succ 1 ser realizada primeiro e retornar um inteiro (2), fazendo com que a composio
do primeiro succ seja feita com um nmero inteiro e no com uma funo. Neste caso, os
parnteses devem ser utilizados para resolver ambigidades.

4.8.2

Esquema de provas usando composio

Seja a funo twice f = f . f. Assim


(twice succ) 12
= (succ . succ) 12
= succ (succ 12)
= succ 13 = 14

--pela definicao de twice


--pela definicao de .

Pode-se generalizar twice, indicando um parmetro que informe quantas vezes a funo deve
ser composta com ela prpria:
iter :: Int -> (t -> t) -> (t -> t)
iter 0 f = id
iter n f = f >.> iter (n - 1) f
Por exemplo, podemos definir 2n como iter n duplica e vamos mostrar a seqncia de
operaes para n=2.
iter 2 duplica

=
=
=
=
=

duplica >.> iter 1 duplica


duplica >.> (duplica >.> iter 0 f)
duplica >.> (duplica >.> id))
duplica >.> duplica
twice duplica

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)
=fx

pela definio de composio


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:
Princpio da extensionalidade: Duas funes, f e g, so iguais se elas produzirem
exatamente os mesmos resultados para os mesmos argumentos".
Princpio da intencionalidade: Duas funes, f e g so iguais se tiverem as mesmas
definies".
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 estivermos interessados na eficincia ou outros aspectos de desempenho de programas devemos usar a
intencionalidade.
Exerccios
1. Mostre que a composio de funes associativa, ou seja, f, g e h, f . (g . h) = (f .
g) . h
2. Prove que n Z + , iter n id = id.
3. Duas funes f e g so inversas se f . g = id e g . f = id. Prove que as funes curry e
uncurry, definidas a seguir, so inversas.
curry :: ((t, u) -> v) -> (t -> u -> v)
curry f (a, b) = f a b
uncurry :: (t -> u -> v) -> ((t, u) -> v)
uncurry g a b = g (a, b)
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 [ ] = [ ]
map f (a : x) = f a : map f x
(f . g) x = f (g x)

(1)
(2)
(3)

Prova:
Caso base: a lista vazia, [ ]:
Lado esquerdo
map (f . g) [ ] = [ ]

por (1)

Lado direito
(map f . map g) [ ]
= map f (map g [ ])
=map f [ ]
=[ ]
110

por (3)
por (1)
por (1)

Passo indutivo: (a : x)
Lado esquerdo
map (f . g) (a : x)
=(f . g) a : map (f . g) x
=f (g a) : map (f . g) x
=f (g a) : (map f . map g) x

(2)
(3)
(hi)

Lado direito
(map f . map g)(a:x)
= map f (map g (a:x))
=map f ((g a) : (map g x))
=f (g a) : (map f ( map g) x)
=f (g a) : (map f . map g) x

(3)
(2)
(3)

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).

4.9

Aplicao parcial

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:
multiplica :: Int -> Int -> Int
multiplica a b = a * b
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:
dobraLista :: [Int] -> [Int]
dobraLista = map (multiplica 2)
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,
multiplica
multiplica
dobraLista
dobraLista

2 :: Int -> Int


2 3 :: Int
:: [Int] -> [Int]
[2,3,5] :: [Int]

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
multiplica 4 :: Int -> Int
multiply 4 5 :: Int

ou

multiplica :: Int -> (Int -> 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).

4.9.1

Seo de operadores

Uma decorrncia direta das aplicaes parciais que representa uma ferramenta poderosa e elegante 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,
(+2) a funo que adiciona algum argumento a 2,
(2+) a funo que adiciona 2 a algum argumento,
(>2) a funo que retorna True se um inteiro for maior que 2,
(3:) a funo que coloca o inteiro 3 na cabea de uma lista,
(++\n) a funo que coloca o caractere \n ao final de uma string.
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:
map (+1) > . > filter ( >0).
dobra = map (*2).
pegaPares = filter (( ==0) . (mod2)).
A funo inquilinos, definida no incio deste Captulo, normalmente, escrita da seguinte
forma:
inquilinos db pes = map snd (filter ehPes db)
where
ehPes (p, b) = (p == pes).
No entanto, ela pode ser escrita em forma de seo da seguinte maneira, o que a torna
muito mais elegante.
inquilinos db pes = map snd (filter ((==pes) . fst) db).
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.
type Doc
type Linha
type Palavra
fazIndice ::

= String
= String
= String
Doc -> [ ([Int], Palavra) ]

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:
Dividir doc em linhas, ficando assim:
["Eu nao sou cachorro nao",
"Pra viver tao humilhado",
"Eu nao sou cachorro nao",
"Para ser tao desprezado"]
Esta operao realizada por divTudo :: Doc > [Linha]
Agora devemos emparelhar cada linha com seu nmero de linha:
[(1, "Eu nao sou cachorro nao"),
(2, "Pra viver tao humilhado"),
(3, "Eu nao sou cachorro nao"),
(4, "Para ser tao desprezado")]
Esta operao realizada por numLinhas :: [Linha] > [(Int, Linha)]
Temos agora que dividir as linhas em palavras, associando cada palavra com o nmero da
linha onde ela ocorre
[(1, Eu"), (1, "nao"), (1, "sou"), (1, "cachorro"), (1, "nao"),
(2, "Pra"), (2, "viver"), (2, "tao"), (2, "humilhado"),
(3, "Eu"), (3, "nao"), (3, "sou"), (3, "cachorro"), (3, "nao"),
(4, "Para"), (4, "ser"), (4, "tao"), (4, "desprezado")]
Esta operao realizada por todosNumPal :: [(Int, Linha)] > [(Int, Palavra)]
Agora necessrio ordenar esta lista em ordem alfabtica das palavras e, se a mesma
palavra ocorre em mais de uma linha, ordenar por linha.
[(1, "cachorro"), (3, "cachorro"), (4, "desprezado"), (1, "Eu"), (3, "Eu"),
(2, "humilhado"), (1, "nao"), (3, "nao"), (4, "Para"), (2, "Pra"), (4, "ser"),
(1, "sou"), (3, "sou"), (2, "tao"), (4, "tao"), (2, "viver")]
Esta operao realizada por ordenaLista :: [(Int, Palavra)] > [(Int, Palavra)]
Agora temos que modificar a lista de forma que cada palavra seja emparelhada com a lista
unitria das linhas onde ela ocorre:
[([1], "cachorro"), ([3], "cachorro"), ([4], "desprezado"), ([1], "Eu"), ([3], "Eu"),
([2], "humilhado"), ([1], "nao"), ([3], "nao"), ([4], "Para"), ([2], "Pra"), ([4], "ser"),
([1], "sou"), ([3], "sou"), ([2], "tao"), ([4], "tao"), ([2], "viver")]
Esta operao realizada por fazListas :: [(Int, Palavra)] > [([Int], Palavra)]
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)]
Usando composio avanada, para ficar mais claro o exemplo, temos:
fazIndice = divideTudo
numDeLinhas
todosNumPal
ordenaLista
fazListas
mistura
diminui

>.>
>.>
>.>
>.>
>.>
>.>

--------

Doc
[Linha]
[(Int, Linha)]
[(Int, Palavra)]
[(Int, Palavra)]
[([Int], Palavra)]
[([Int], Palavra)]

->
->
->
->
->
->
->

[Linha]
[(Int, Linha)]
[(Int, Palavra)]
[(Int, Palavra)]
[([Int], Palavra)]
[([Int], Palavra)]
[([Int], Palavra)]

Agora cada uma destas funes sero definidas.


divTudo :: Doc -> [Linha]
divTudo = tiraEspaco >.> formaLinhas
tiraEspaco :: Doc -> Doc
tiraEspaco [ ] = [ ]
tiraEspaco (a : x)
|a ==
= tiraEspaco x
|otherwise
= (a : x)
pegaLinha :: Doc -> Linha
pegaLinha [ ] = [ ]
pegaLinha (a : x)
|a /= \n = a : pegaLinha x
|otherwise = [ ]
tiraLinha :: Doc -> Doc
tiraLinha [ ] = [ ]
tiraLinha (x : xs)
|x /= \n = tiraLinha xs
|otherwise = xs
formaLinhas :: Doc -> [Linha]
formaLinhas [ ] = [ ]
formaLinhas st = (pegaLinha st) : formaLinhas (tiraEspaco (tiraLinha st))

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.
numLinhas :: [Linha] -> [(Int, Linha)]
numLinhas lin = zip [1 .. length lin] lin
Vamos considerar, inicialmente, apenas uma linha:
numDePalavras :: (Int, Linha) -> [(Int, Palavra)]
numDePalavras (num, linha) = map poeNumLinha (divideEmPalavras linha)
where poeNumLinha pal = (num, pal)
divideEmPalavras :: String -> [Palavra]
divideEmPalavras st = divide (tiraEspaco st)
divide :: String -> [Palavra]
divide [ ] = [ ]
divide st = (pegaPalavra st) : divide (tiraEspaco (tiraPalavra st))
pegaPalavra :: String -> String
pegaPalavra [ ]
= [ ]
pegaPalavra (a : x)
|elem a espacoEmBranco = [ ]
|otherwise
= a : pegaPalavra x
tiraPalavra :: String -> String
tiraPalavra [ ] = [ ]
tiraPalavra (a : x)
|elem a espacoEmBranco = (a : x)
|otherwise
= tiraPalavra x
espacoEmBranco = [\n, \t, ]
todosNumPal :: [(Int, Linha)] -> [(Int, Palavra)]
todosNumPal = concat . map numDePalavras
Agora vamos definir a funo de ordenao usando a tcnica aplicada no algoritmo de ordenao 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:
menor :: (Int, Palavra) -> (Int, Palavra) -> Bool
menor (n1, w1) (n2, w2) = w1 < w2 || (w1 == w2 && n1 < n2)
A funo ordenaLista deve ser definida de forma que pares repetidos no apaream duplicados. 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.
ordenaLista :: [(Int, Palavra)] -> [(Int, Palavra)]
ordenaLista [ ]
= [ ]
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:
fazListas :: [(Int, Palavra)] -> [([Int], Palavra)]
fazListas = map mklist where mklist (n, st) = ([n], st)
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:
mistura :: [([Int], Palavra)] -> [([Int], Palavra)]
mistura [ ] = [ ]
mistura [a] = [a]
mistura ((l1, w1) : (l2, w2) : rest)
|w1 /= w2 = (l1, w1) : mistura ((l2, w2) : rest)
|otherwise = mistura ((l1 ++ l2, w1) : rest)
Finalmente, sero retiradas da lista as palavras com menos de 3 letras. Esta operao foi colocada apenas para efeito de mostrar como pode ser definda, mas, na realidade, ela dispensvel.
Esta operao definida da seguinte forma:
diminui = filter tamanho
where tamanho (n1, pal) = length pal > 3
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
C
E
U
E
O
A
V
V
M
A
E
A

4
O
U
E

3
M

A
R
I
O
A
D
D
V
M

M
D
U
R
A
I
O
A

5
P
S
V
M
T
I
A

D
U
R

8
U
E
O
A
O
N
T
E
D
A

7
T
I
U
R
D
H
E
E
M
E
T

2
E

6
R
Q
T
P

A
A
U

S
E
E

A
C
P
U

Desta forma, a mensagem cifrada deve ser a seguinte, esclarecendo que cada espao em branco
tmbm considerado um caractere.
"EUEOAVVMAE A
AAU SEE
EME T EOAON TEDA
"

A MDURAIOAUE R IOADDVMSVMTIA

DURQTP

ACPU

IURDHE

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:
div_em_col :: String -> Int -> [String]
div_em_col [ ] = [ ]
div_em_col str tam = (pega_col str tam) : div_em_col (tira_col str tam) tam
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.
pega_col :: String -> Int -> String
pega_col [ ] _ = [ ]
pega_col (x : xs)
|tam > 0
= x : pega_col xs )tam - 1)
|otherwise = [ ]
tira_col :: String -> Int -> String
tira_col [ ] _ = [ ]
tira_col (x : xs) tam
|tam > 0
= tira_col xs (tam - 1)
|otherwise = (x : xs)
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.
pega_lista_pos :: [Char] -> [(Char, Int)] -> [Int]
pega_lista_pos [] _ = [ ]
pega_lista_pos (x : xs) l = (pega_pos x l) : pega_lista_pos xs l
A funo pega_pos responsvel por retornar a posio em que cada caractere tem na
chave.
pega_pos :: Char -> [(Char, Int)] -> Int
pega_pos c ((a, n) : x)
|c == a
= n
|otherwise = pega_pos c x
A construo da lista contendo todas as colunas na ordem em que foram construdas feita
pela funo lista.
lista :: [String] -> [Int] -> [String]
lista ls [ ] = [ ]
lista ls (b : y) = (ls !! b) : lista ls y
A funo lista_int utiliza a funo lista com o texto cifrado dividido em palavras com o
tamanho de cada coluna.
lista_int :: String -> String -> [String]
lista_int txt ch =
lista (div_em_pal txt (div (length txt) (length ch))) (lista_pos ch)
A funo pega constri cada linha da tabela, tomando os caracteres de mesma posio em
cada coluna, formando uma linha da tabela original.
pega :: Int -> [String] -> String
pega _ [ ] = [ ]
pega n (x : xs) = (x !! n) : (pega n xs_
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
Finalmente a String inicial reconstruda usando a funo mens_original.
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 momento. 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.
type Jarro = (Int, Int, Int)
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:
jogaAemB :: Jarro -> [Jarro]
jogaAemB (x, y, z)
|(x == 0) || (y == 5) = [ ]
|x + y > 5 = [(x+y-5, 5, z)]
|otherwise = [(0, x+y, z)]
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:
jogaAemC :: Jarro -> [Jarro]
jogaAemC (x, y, z)
|(x == 0) || (z == 3) = [ ]
|x + z > 3 = [(x+z-3, y, 3)]
|otherwise = [(0, y, 3)]
jogaBemA :: Jarro -> [Jarro]
jogaBemA (x, y, z)
|(y == 0) || (x == 8) = [ ]
|x + y > 8 = [(8, x+y-8, z)]
|otherwise = [(x+y, 0, z)]
jogaBemC :: Jarro -> [Jarro]
jogaBemC (x, y, z)
|(y == 0) || (z == 3) = [ ]
119

|y + z > 3 = [(x, y+z-3, 3)]


|otherwise = [(x, 0, z+y)]
jogaCemA :: Jarro -> [Jarro]
jogaCemA (x, y, z)
|(z == 0) || (x == 8) = [ ]
|z + x > 8 = [(8, y, x+z-8)]
|otherwise = [(x+z, y, 0)]
jogaCemB :: Jarro -> [Jarro]
jogaCemB (x, y, z)
|(z == 0) || (y == 5) = [ ]
|z + y > 5 = [(x, 5, z+y-5)]
|otherwise = [(x, y+z, 0)]
Dessa forma, os estados possveis a partir de uma determinada configurao sero os seguintes:
possiveis_sucessores :: Jarro -> [Jarro]
possiveis_sucessores j = (jogaAemB j) ++ (jogaAemC j) ++ (jogaBemA j) ++
(jogaBemC j) ++ (jogaCemA j) ++ (jogaCemB j)
No entanto alguns destes estados no interessam se eles j estiverem na sequncia de estados.
Assim, os sucessores de uma determinada sequncia devem ser escolhidos de forma a no se
repetir uma configurao. A operao pertence verifica se uma configurao j se encontra na
sequncia dada. Se este for o caso, a sequncia descartada.
sucessores :: [Jarro] -> [Jarro] -> [[Jarro]]
sucessores _ [ ] = [ ]
sucessores ls (j : js)
|pertence j ls = sucessores ls js
|otherwise
= ([j] ++ ls) : sucessores ls js
pertence :: Jarro -> [Jarro] -> Bool
pertence j [ ] = False
pertence (x,y,z) ((m, n, k): xs)
|(x == m) && (y == n) && (z == k) = True
|otherwise = pertence (x,y,z) xs
A funo achou verifica se uma sequncia com a especificao desejada foi encontrada.
achou :: [[Jarro]] -> Jarro -> [Jarro]
achou [ ] _ = [ ]
achou (l:ls) jr
|pertence (head l) [jr] = reverse l
|otherwise = achou ls jr
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:
map_suc :: [[Jarro]] -> [[Jarro]]
map_suc [ ] = [ ]
map_suc (l:la) = (sucessores l (possiveis_sucessores (head l))) ++ map_suc la
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:
resultado :: [[Jarro]] -> Jarro -> [[Jarro]]
resultado [ ] _ = error "Este jarro nao pode ser atingido a partir deste jarro inicial"
resultado (l:ls) jr
|res_int /= [ ] = [res_int]
|otherwise = resultado (map_suc (l:ls)) jr
where res_int = achou (l:ls) jr
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.
4. Encontre todas as solues possveis para se colocar 8 rainhas em um tabuleiro de xadrez
de forma que nenhuma delas ataque qualquer uma outra.
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:
a. Dado o formato do nmero (lista de inteiros) gerar a string correspondente de *s e
espaos.
toString :: [Int] -> String
toString nove ==> "**** ****

*"

b. Faa uma funo que transforma a string de *s e espaos em uma lista de strings,
cada uma representando uma linha do LCD:
type Linha = String
toLinhas :: String -> [Linha]
toLinhas "**** **** * *"
==> ["***", "* *","***", "

*", "

*"]

c. Faa uma funo que pegue uma lista de strings e a transforme em uma nica string
com o caractere n entre elas:
showLinhas :: [Linha] -> String
showLinhas ["***", "* *", "***", "
==> "***\n* *\n***\n *\n

*", "
*"

*"]

d. Faa uma funo que pegue duas listas de linhas e transforme-as em uma nica lista
de linhas, onde as linhas originais se tornam uma nica, com um espao entre elas:
juntaLinhas :: [LInha] -> [Linha] -> [Linha]
juntaLInhas ["***", "* *", "***", " *", " *""]
["***", "* *", "***", " *"," *"]
==> ["*** ***", "* * * *", "*** ***",
" *
*", " *
*"]
e. Faa uma funo que, dado um inteiro, imprima-o usando *s, espaos e caracteres
ns. A funo tem que funcionar para inteiros de 2 e 3 dgitos.
tolcd :: Int -> String
Dica: use as funes div e mod de inteiros, a funo (!!) e a lista numeros, dada
a seguir:
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, representado 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 linguagem. 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

Tipos algbricos de dados


Algebraic data types provides
a single powerfull way to describe data types.
Other languages often need several different
features to achive the same degree of expressiveness.
(Bryan OSullivan in [29])

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 possibilidades 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.

5.2

Classes de tipos

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.

5.2.1

Fundamentao das classes

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.
elem :: t -> [t] -> Bool
elem x [ ] = False
elem x (a : y) = x == a || elem x y
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:
(/=), (==) :: Bool -> Bool -> Bool
x == y = (x and y) or (not x and not y)
x /= y = not (x == y)
importante observar a diferena entre == e =. O smbolo == usado para denotar um
teste computacional para a igualdade, enquanto o smbolo = usado nas definies e no sentido
matemtico normal. Para a Matemtica, a assertiva double = square uma afirmao falsa
e a assertiva = verdadeira, uma vez que qualquer coisa igual a si prpria. No entanto,
para a Computao, as funes no podem ser testadas quanto a sua igualdade e o resultado
da avaliao == tambm e no True. Isto no quer dizer que o avaliador seja uma
mquina matemtica, mas que seu comportamento descrito por um conjunto limitado de regras
matemticas, escolhidas de forma que elas possam ser executadas por um computador [4].
O objetivo principal de se introduzir um teste de igualdade poder us-lo em uma variedade
de tipos distintos, no apenas no tipo Bool. Em outras palavras, desejamos que == e /= sejam
operadores sobrecarregados. Estas operaes sero implementadas de forma diferente para cada
par de tipos e a forma adequada de introduz-las declarar uma classe que contenha todos
os tipos para os quais == e /= vo ser definidas. Esta classe pr-definida em Haskell e
denominada Eq.
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:
(==), (/=) :: Eq t => t -> t -> Bool
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.

5.2.2

Funes que usam igualdade

Analisemos agora a funo todosIguais que verifica se trs valores inteiros so iguais, ou no.
todosIguais :: Int -> Int -> Int -> Bool
todosIguais m n p = (m == n) && (n == p)
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:
todosIguais :: Eq t => t -> t -> t -> Bool
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:
suc :: Int -> Int
suc = (+1)
Vejamos o que acontece ao se chamar a funo todosIguais suc suc suc.
O resultado mostrado pelos compiladores Haskell ou Hugs ERROR: Int > Int is not
an instance of class Eq, significando que no existe uma definio do teste de igualdade para
funes do tipo Int > Int (o tipo de suc).
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.
class Visible t where
toString :: t -> String
size :: t -> Int
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
instance Visible Char where
toString ch = [ch]
size _ = 1
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
instance Visible Bool where
toString True = "True"
toString False = "False"
size _ = 1
Para que o tipo Bool seja uma instncia da classe Eq devemos fazer:
instance Eq Bool where
True == True = True
False == False = True
_ == _ = False

5.2.4

Classes derivadas

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:
class Eq t => Ord t where
(<), (<=), (>), (>=) :: t -> t -> Bool
max, min :: t -> t -> t
compare :: t -> t -> Ordering
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:
vSort = toString . iSort
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
vSort :: (Ord t, Visible t) => [t] -> String
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 discordncias entre os projetistas de linguagens em relao a este tema. A herana mltipla tambm pode
ocorrer em uma declarao de instncia como:
instance (Eq a, Eq b) => Eq (a, b) where
(x, y) == (z, w) = (x == z) && (y == w)
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,
class (Ord a, Visible a) => OrdVis a
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 assinatura 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
vSort :: OrdVis t => [t] -> String
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.
3. Qual o tipo da funo compare x y = size x <= size y?
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:
class (Eq t) => Ord t where
compare :: t -> t -> Ordering
(<), (<=), (>=), (>) :: t -> t -> Bool
max, min :: t -> t -> t
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
|x < y
|otherwise

= EQ
= LT
= GT

A vantagem de se usar a funo compare que muitas outras funes podem ser definidas
em funo dela, por exemplo,
x
x
x
x

<= y
< y
>= y
> y

=
=
=
=

compare
compare
compare
compare

x
x
x
x

y
y
y
y

/=
==
/=
==

GT
LT
LT
GT

As funes max e min so definidas por


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:
class (Ord t) => Enum a where
toEnum :: Int -> t
fromEnum :: t -> Int
enumFrom :: t -> [t]
enumFromThen :: t -> t -> [t]
enumFromTo :: t -> t -> [t]
enumFromThenTo :: t -> t -> t -> [t]

-----

[m ..]
[m, n ..]
[m .. n]
[m, m .. n]

As funes fromEnum e toEnum tm as funes ord e chr do tipo Char como correspondentes, 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:
class Bounded t where
minBound, maxBound :: t
que retornam os valores mnimos e mximos de cada tipo.
A classe Show
Esta classe contm os tipos cujos valores podem ser escritos como String. Em Haskell,
apenas os Strings podem ser mostrados. Portanto, se um valor no for um String, necessrio
que ele seja transformado antes em um String para depois poder ser mostrado. A maioria dos
tipos pertence a esta classe. Sua definio a seguinte:
type ShowS = String -> String
class Show a where
showsPrec :: Int -> a -> ShowS
show :: a -> String
showList :: [a] -> ShowS
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.
read :: Read t => String -> t
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

Functor

showPrec
show
showList

(==) (/=)

fmap

Monad

Num

Ord

(>>=) (>>)
return
fail

(+) () (*)
negate
abs signum
fromInteger

compare
(<) (<=) (>=) (>)
max min

MonadPlus

Ix
range
index
inRange
rangeSize

(/)
recip
fromRational
fromDouble

toRational

Enum
succ pred
toEnum
fromEnum
enumFrom
enumFromThen
enumFromTo
enumFromThenTo

Integral

mZero
mPlus

Fractional

Real

RealFrac
properFraction
truncate
round
ceiling
floor

Read

quot rem div mod


quotRem divMod
even odd
toInteger

readsPrec
readList

Bounded
minBound maxBond

Floating
pi
exp log sqrt
(**) logBase
sin cos tan
sinh cosh tanh
asinh acosh atanh

RealFloat
floatRadix
floatDigits
floatRange
decodeFloat
encodeFloat
exponent
significand
scaleFloat
isNaN isInfinite
isDenormalized
isNegativeZero
isIEEE
atan2

Figura 5.1: Resumo da hierarquia de classes em Haskell.

5.3

Tipos algbricos

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:
as enumeraes implementadas em algumas linguagens de programao;
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 elementos 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.

5.3.1

Como se define um tipo algbrico?

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
data <Const_tipo> = <Const_Val1> | <Const_Val2> | ...

| <Const_Valn>

onde Const_tipo o construtor do tipos e Const_Val1, Const_Val2, ... Const_Valn so


os elementos ou valores do tipo. Cada valor do tipo composto de um construtor de
valor, tambm conhecido por construtor de dado, seguido, ou no, de componentes do
valor. Vamos mostrar um exemplo caracterizando cada um destes elementos.
data Publicacao = Livro Int String [String]
| Revista String Int Int
| Artigo String String Int
deriving (Show)
Neste caso, Publicacao o construtor do tipo e Livro, Revista e Artigo so os construtores
dos valores ou dos dados. Os tipos Int, String e [String] so os componentes do valor Livro.
De forma similar, String, Int e Int so os componentes do valor Revista e String, String
e Int so os componentes do valor Artigo. A declarao deriving (Show) que complementa
esta declarao de tipo informa que este tipo de dado deve pertencer a classe Show.
Os componentes dos valores exercem os mesmos papeis que os campos dos registros nas
linguagens de programao tradicionais. O componente Int seguindo o construtor do valor
Livro reservado para ser uma chave que identifica o livro em uma biblioteca ou em um Banco
de Dados. O componente String deve ser utilizado para identificar o nome do livro e a lista
[String] reservada para representar os autores do livro.
Agora possvel criar uma instncia de uma Publicacao da seguinte forma:
pub = Livro 9780596514983 "Real World Haskell"
["Bryan OSullivan", "John Goerzen", "Don Stewart"]
Em ghci, o tipo do construtor Livro pode ser mostrado da seguinte forma:
<ghci>: type Livro
Livro :: Int -> String -> [String] -> Publicacao
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:
enum dia_util {segunda, terca, quarta, quinta, sexta};
Este tipo pode ser definido, em Haskell, como um tipo algbrico, com a vantagem da ausncia
dos bugs citados. Seno vejamos:
data Dia_util =
|
|
|
|

Segunda
Terca
Quarta
Quinta
Sexta
deriving (Eq, Show)

O tipo unio livre


A unio livre um tipo de dado implementado em C e C++ que pode ser usado para
modelar vrias alternativas. Em C e C++, no informada qual alternativa est presente. Isto
significa que a unio livre representa uma fonte de erros de programao porque o type checker
no pode fica impossibilitado de realizar seu trabalho. Seja, por exemplo, as declaraes a seguir:
enum forma {circulo, retangulo};
struct ponto {float x, y};
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)


data Figura = Circulo Ponto Float
| Retangulo 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,
type Nome = String
type Idade = Int
data Gente = Pessoa Nome Idade
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:
Pessoa "Barack Obama" 40
Pessoa "Mahatma Ghandi" 71
Podemos formar uma funo mostraPessoa que toma um valor do tipo Gente e o mostra na
tela:
mostraPessoa :: Gente -> String
mostraPessoa (Pessoa n a) = n ++ " -- " ++ show a
A aplicao da funo mostraPessoa (Pessoa "John Lennon", 50) ser respondida por
>"John Lennon -- 50"
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
type Gente = (Nome, Idade)
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 determinadas 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.

5.3.2

Tipos recursivos

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
Alguns exemplos de utilizao deste tipo de dado podem ser:
2 modelado por Lit 2
2 + 3 modelado por Add (Lit 2) (Lit 3)
(3 1) + 3 modelado por Add (Sub (Lit 3) (Lit 1)) (Lit 3)
Podemos criar uma funo de avaliao que tome como argumento uma expresso e d como
resultado o valor da expresso. Para isso podemos construir a funo eval da seguinte forma:
eval
eval
eval
eval

:: Expr
(Lit n)
(Add e1
(Sub e1

-> Int
= n
e2) = (eval e1) + (eval e2)
e2) = (eval e1) - (eval e2)

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:
mostraExpressao :: Expr -> String
mostraExpressao (Lit n) = show n
mostraExpressao (Add e1 e2)
= "(" ++ mostraExpressao e1 ++ "+" ++ mostraExpressao e2 ++ ")"
mostraExpressao (Sub e1 e2)
= "(" ++ mostraExpressao e1 ++ "-" ++ mostraExpressao e2 ++ ")"
Exerccios:
1. Construir uma funo que calcule o nmero de operadores em uma expresso.
2. Calcule:
136

eval (Lit 67)


eval (Add (Sub (Lit 3) (Lit 1) (Lit 3))
mostraExpressao (Add (Lit 67) (Lit (-34))).
3. Adicione as operaes de diviso e multiplicao de inteiros ao tipo Expr e redefina as
funes eval e mostraExpressao, vistas anteriormente. O que sua operao de diviso
faz no caso do divisor ser zero?
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.
data Expr = Lit Int
|Op Ops Expr Expr
|If BExp Expr Expr
A expresso If b e1 e2 tem o valor e1 se b tiver o valor True e tem o valor e2 se b for
False.
data BExp = BoolLit Bool
|And BExp BExp
|Not BExp
|Equal Expr Expr
|Greater Expr Expr
Estas cinco clusulas do os seguintes valores:
Literais booleanos: BoolLit True e BoolLit False.
A conjuno de duas expresses: True se as duas sub-expresses argumentos tiverem
o valor True, caso contrrio, o resultado ser False.
A negao de uma expresso: Not x tem o valor True se x for False.
A igualdade de duas expresses: Equal e1 e2 True se as duas expresses numricas
tiverem valores iguais.
A ordem maior: Greater e1 e2 True se a expresso numrica e1 tiver um valor
maior que o da expresso numrica e2.
A partir destes pressupostos, defina as funes:
eval :: Expr -> Int
bEval :: BExp -> Bool
por recurso e estenda a funo show para mostrar o tipo re-definido para expresses.

5.3.3

Tipos algbricos polimrficos

As definies de tipos algbricos podem conter tipos variveis como t, u, etc. Por exemplo,
data Pares t = Par t t
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:
igualPar :: Eq t => Pares t -> Bool
igualPar (Par x y) = (x == y)
As listas podem ser construdas a partir de tipos algbricos.
data List t = Nil | Cons t (List t)
deriving (Eq, Ord, Show)

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.

5.4.1

rvores binrias

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:
data ArvBin t = Folha t | No (ArvBin t) (ArvBin t)
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 distintas. 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.

5.4.2

Funes sobre rvores binrias

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

09

51

19

51
a)

09

14

19

09

51

14

19

b)

51

19

19

09
c)

14

d)

09
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, :
tamArvBin :: ArvBin t -> Int
tamArvBin (Folha a) = 1
tamArvBin (No ae ad) = tamArvBin ae + tamArvBin ad
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:
altArvBin :: ArvBin t -> Int
altArvBin (Folha a) = 0
altArvBin (No ae ad) = 1 + max (altArvBin ae) (altArvBin ad)
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:
arvBintolista :: ArvBin t -> [t]
arvBintolista (Folha a) = [a]
arvBintolista (No ae ad) = arvBintolista ae ++ arvBintolista ad
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:
nosint :: ArvBin t -> Int
nosint (Folha a) = 0
nosint (No ae ad) = 1 + nosint ae + nosint ad
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:
prof :: ArvBin t -> Int
prof (Folha a) = 0
prof (No ae ad) = 1 + max (prof ae) (prof ad)
Diz-se que uma rvore binria perfeita se todas as suas folhas tiverem a mesma profundidade, 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:
1. Caso (Folha a). Para este caso, temos:
altura (F olha a) = 0
tamanho (F olha a) = 1
altura (F olha a) < tamArvBin (F olha a)
tamArvBin (F olha a) = 1 = 20 = 2(altura (F olha a))

pela definio de altArvBin


pela definio de tamArvBin

Assim, a assertiva vlida para o caso (Folha a).


2. Caso (No ae ad). Neste caso, temos:
tamArvBin (N o ae ad)
= tamArvBin ae + tamArvBin ad
2(altArvBin ae) + 2(altArvBin ad)
2h + 2h
= 2(h + 1)

pela definio de tamArvBin


pela hiptese de induo
sendo h = max (altArvBin ae) (altArvBin ad)

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:
fazArvBin :: [t] -> ArvBin t
fazArvBin xs
= if m == 0
--se xs s tiver um elemento
then Folha (head xs)
else No (fazArvBin xe) (fazArvBin xd)
where (xe, xd) = (take m xs, drop m xs)
m = div (length xs) 2
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.
data ArvBinA t = Folha t | No Int (ArvBinA t) (ArvBinA t)
Nesta definio, os construtores dos valores (Folha e No) so os mesmos adotados na
definio do tipo ArvBin. No entanto, eles devem ser diferenciados em scripts em que os
dois tipos de rvores estejam presentes, para evitar ambigidade. Para rotular os ns internos
de uma rvore binria aumentada com valores inteiros representando os tamanhos das subrvores relacionadas a cada n, devemos redefinir a funo tamArvBin feita para a rvore binria
ArvBin t.
tamArvBinA :: ArvBinA t -> Int
tamArvBinA (Folha x) = 1
tamArvBinA (No n ae ad) = n
Os ns internos sero rotulados utilizando a funo rotula definida da seguinte forma:
rotula :: ArvBinA t -> ArvBinA t -> ArvBinA t
rotula ae ad = No n ae ad where n = tamArvBinA ae + tamArvBinA ad
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:
fazArvBinA :: [t] -> ArvBinA t
fazArvBinA xs
|(m==0)
= Folha (head xs)
--se a lista xs for unitria
|otherwise = rotula (fazArvBinA xe) (fazArvBinA xd)
where m = div (length xs) 2
(xe, xd) = (take m xs, drop m xs)
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:
arvBinAtoLista :: ArvBinA t -> [t]
arvBinAtoLista (Folha x) = [x]
arvBinAtoLista (No n ae ad) = (arvBinAtoLista ae) ++ (arvBinAtoLista ad)
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.
recupera :: ArvBinA t -> Int -> t
recupera a k = (arvBinAtoLista a)!!k
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:
(xs ++ ys)!!k = if k<m then xs!!k else ys!!(k-m)
where m = length xs
Agora podemos implementar a funo recupera de forma mais eficiente, transformando-a
em recupABA da seguinte maneira:
recupABA :: ArvBinA t -> Int -> t
recupABA (Folha a) 0 = a
recupABA (No m ae ad) k
= if k < m then recupABA ae k else recupABA ad (k-m)
Esta nova definio, recupABA, usa a informao do tamanho da subrvore que est armazenada 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]).
1. Quantas rvores existem de tamanho 5? Escreva um programa para calcular o nmero de
rvores binrias de tamanho n, para um dado n N.
2. Prove que o nmero de folhas em uma rvore binria sempre igual a 1 mais o nmero de
ns internos.
3. As subrvores de uma rvore binria podem ser definidas por
subarvores :: ArvBin t -> [ArvBin t]
subarvores (Folha x) = [Folha x]
subarvores (No xt yt) = [No xt yt] ++ subarvores xt ++ subarvores yt
Estabelea e prove o relacionamento entre length(subarvores xt) e tamanho xt.
4. A rvore aumentada tambm pode ser construda inserindo-se em cada n interno a informao da profundidade deste n. Faa esta definio.

5.4.4

rvores de buscas binrias

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:
data (Ord t) => ArvBusBin t = Nil | No (ArvBusBin t) t (ArvBusBin t)
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 informaes 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 transformam 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 ordenada 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.
arvBBtolista :: (Ord t) => ArvBusBin t -> [t]
arvBBtolista Nil = [ ]
arvBBtolista (No ae n ad) = arvBBtolista ae ++ [n] ++ arvBBtolista ad
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:
membroABB :: (Ord t) => ArvBusBin t -> Bool
membroABB x Nil = False
membroABB x (No ae n ad)
|(x<n)
= membroABB x ae
|(x==n)
= True
|(x>n)
= membroABB x ad
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:
altABB :: (Ord t) => ArvBusBin t -> Int
altABB Nil
= 0
altABB (No ae n ad)
|(ae == Nil) && (ad == Nil) = 0
|otherwise = 1 + maximo (altABB ae) (altABB ad)
Uma rvore de busca binria de tamanho n e altura h satisfaz a seguinte relao:
dlog(n + 1)e h < n + 1
Uma rvore de busca binria pode ser construda a partir de uma lista de valores. Para
isto, necessrio modificar a definio da funo fazArvBin, transformando-a em fazABB, da
seguinte forma:
fazABB :: (Ord t) => [t] -> ArvBusBin t
fazABB [ ]
= Nil
fazABB (x:xs) = No (fazABB ys) x (fazABB zs)
where (ys, zs) = particao (<=x) xs
onde a funo particao definida por:
particao :: (t -> Bool) -> [t] -> ([t], [t])
particao p xs = (filter p xs, filter (not.p) xs)
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.
sort :: (Ord t) => [t] -> [t]
sort = arvBBtolista . fazABB
Se for eliminada a rvore intermediria desta definio, ela se transforma em uma definio
da funo quicksort, bastante utilizada na ordenao de listas.
Insero e remoo em rvores de busca binria
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:
insere :: (Ord t) => t -> ArvBusBin t -> ArvBusBin t
insere x Nil = No Nil x Nil
insere x (No ae n ad)
|(x<n) = No (insere x ae) n ad
|(x==n) = No ae n ad
|(x>n) = No ae n (insere x ad)
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:
remove :: (Ord t) => t -> ArvBusBin t -> ArvBusBin t
remove x Nil = Nil
remove x (No ae n ad)
|(x<n) = No (remove x ae) n ad
|(x>n) = No ae n (remove x ad)
|(x==n) = junta ae ad
A funo auxiliar junta ae ad tem a responsabilidade de unir as duas subrvores ae e ad,
satisfazendo a seguinte propriedade:
arvBBtolista (junta ae ad) = arvBBtolista ae ++ arvBBtolista ad
A juno das sub-rvores ae e ad requer que seja escolhido um valor para ser a nova raiz da
rvore. As escolhas possveis so o maior valor da sub-rvore ae ou o menor valor da sub-rvore
ad. No nosso caso, escolhemos o menor valor da sub-rvore da direita, ad, que deve ser retirado
de sua posio inicial para ocupar a posio da raiz que foi removida. Esta juno pode ser feita
da seguinte forma:
junta
junta
junta
junta

:: (Ord t) => ArvBusBin t -> ArvBusBin t -> ArvBusBin t


Nil ad = ad
ae Nil = ae
(No aee x aed) (No ade y add)
= (No aee x aed) k (remove k (No ade y add))
144

where k = minArv (No ade y add)


minArv :: (Ord t) => ArvBusBin t -> t
minArv (No Nil x _) = x
minArv (No xt _ _) = minArv xt
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.

Figura 5.3: Representao grfica da funo junta em ao.

5.4.5

rvores heap binrias

Vamos agora definir um outro tipo de rvore binria conhecida como rvore heap binria, cujo
tipo ser ArvHB.
data (Ord t) => ArvHB t = Nil |No t (ArvHB t) (ArvHB t)
O tipo ArvHB t virtualmente igual ao tipo ArvBusBin t, vista na sub-seo anterior,
com a diferena de que o rtulo em cada n em uma rvore heap binria colocado antes das
subrvores esquerda e direita e no entre elas. Alm disso, a rvore ArvHB t deve satisfazer
condio de que o rtulo em cada n no seja maior que os rtulos de qualquer das suas
subrvores. Isto significa que os rtulos, ao longo de qualquer caminho, a partir da raiz, esto
sempre em ordem no decrescente.
Para formalizar esta condio, vamos definir inicialmente a funo arvHBtolista que transforma uma rvore heap binria em uma lista:
arvHBtolista :: (Ord t) => ArvHB t -> [t]
arvHBtolista Nil = [ ]
arvHBtolista (No n ae ad) = n : merge (arvHBtolista ae) (arvHBtolista ad)
merge
merge
merge
merge

:: (Ord t) => [t] -> [t] -> [t]


[ ] ys = ys
(x : xs) [ ] = x : xs
(x : xs) (y : ys) = if x <= y then x : merge xs (y : ys)
else y : merge (x : xs) ys

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.
fazHB :: (Ord t) => [t] -> ArvHB t
fazHB [ ] = Nil
fazHM (x : xs) = No x (fazHB ys) (fazHB zs)
where (ys, zs) = particao (<x) xs
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:
fazHeap :: (Ord t) => [t] -> ArvHB t
fazHeap = heapfica . fazHB
A funo fazHB constri uma rvore de altura mnima, no necessariamente ordenada, e a
funo heapfica reorganiza os rtulos para garantir a ordenao.
heapfica :: (Ord t) => ArvHB t -> ArvHB t
heapfica Nil = Nil
heapfica (No n ae ad) = desliza n (heapfica ae) (heapfica ad)
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.
desliza :: (Ord t) => t -> ArvHB t -> ArvHB t -> ArvHB t
desliza x Nil Nil = No x Nil Nil
desliza x (No y aee aed) Nil = if x <= y then No x (No y aee aed) Nil
else No y (desliza x aee aed) Nil
desliza x Nil (No z ade add) = if x <= z then No x Nil (No z ade add)
else No z Nil (desliza x ade add)
desliza x (No y aee aed) (No z ade add)
|x <= (min y z) = No x (No y aee aed) (No z ade add)
|y <= (min x z) = No y (desliza x aed aed) (No z ade add)
|z <= (min x y) = No z (No y aed aed) (desliza x ade add)
Finalmente, podemos usar rvores heap binrias para obter uma nova implementao para o
algoritmo de ordenao, conhecido como heapsort:
heapsort :: (Ord t) => [t] -> [t]
heapsort = arvHBtolista.fazHeap
Exerccios (baseados em Richard Bird [4])
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 implementada atravs da funo niveis, definida por
niveis :: [t] -> [[t]]
niveis = niveisCom 1
Defina a funo niveisCom.
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:
(a) contar o nmero de folhas em uma ArvoreGeral,
(b) encontrar a profundidade de uma ArvoreGeral,
(c) somar os elementos de uma rvore genrica,
(d) verificar se um elemento est em uma ArvoreGeral,
(e) mapear uma funo sobre os elementos das folhas de uma ArvoreGeral e
(f) transformar uma ArvoreGeral em uma lista.

5.5

Tratamento de excees

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, normalmente 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.
error :: String -> t
Uma tentativa de avaliar a expresso error "Circulo com raio negativo" resultar na
mensagem
Program error: Circulo com raio negativo.
que impressa e a execuo do programa abortada.
O problema com esta tcnica que todas as informaes usuais da computao calculadas
at o ponto em que ocorreu a exceo so perdidas, porque o programa abortado. Em vez
disso, o erro pode ser tratado de alguma forma, sem parar a execuo do programa. Isto pode
ser feito atravs das duas tcnicas a seguir.
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,
tail :: [t] -> [t]
tail (a : x) = x
tail [ ] = error "cauda de lista vazia"
No entanto, esta definio poderia ser refeita da seguinte forma:
tl :: [a] -> [a]
tl (_:xs) = xs
tl [ ]
= [ ]
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:
divide :: Int -> Int -> Int
divide n m
|(m /= 0) = n div m
|otherwise = 0
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.
hd :: a -> [a] -> a
hd y (x:_) = x
hd y [ ]
= y
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.
data Maybe t = Nothing | Just t
deriving (Eq, Ord, Read, Show)
Na realidade Maybe t o tipo t com um valor extra, Nothing, acrescentado. Vamos
redefinir a funo de diviso, divide, transformando-a em errDiv da seguinte forma:
errDiv :: Int -> Int -> Maybe Int
errDiv n m
| (m /= 0)
= Just (n div m)
| otherwise = Nothing
Para o caso geral, utilizando uma funo f, devemos fazer
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.
A funo mapMaybe repassa o valor de um erro, atravs da aplicao de uma funo g.
Suponhamos que g seja uma funo do tipo a b e que estejamos tentando us-la como
operador sobre um tipo Maybe a. Caso o argumento seja Just x, g ser aplicada a x, ou seja,
g x do tipo b. Se, no entanto, o argumento for Nothing, ento Nothing o resultado.
mapMaybe :: (a -> b) -> Maybe a -> Maybe b
mapMaybe g Nothing = Nothing
mapMaybe g (Just x) = Just (g x)
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 Just, aplica-se a funo g de a para b;
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.
maybe :: b -> (a -> b) -> Maybe a -> b
maybe n f Nothing = n
maybe n f (Just x) = f x
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.
maybe 56 (1+) (mapMaybe (*3) (errDiv 9 0))
= maybe 56 (1+) (mapMaybe (*3) Nothing)
= maybe 56 (1+) Nothing
= 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.
maybe 56 (1+) (mapMaybe (*3) (errDiv 9 1))
= maybe 56 (1+) (mapMaybe (*3) (Just 9))
= maybe 56 (1+) (Just 27)
= (1+) 27
= 28
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.

5.6

Lazy evaluation

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
A segunda seqncia de reduo para a mesma expresso :
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:
fst (quadrado 5, quadrado 7)
= fst (5 * 5, quadrado 7)
= fst (25, quadrado 7)
= fst (25, 7 * 7)
= fst (25, 49)
= 25

------

usando
usando
usando
usando
usando

a
a
a
a
a

definicao
definicao
definicao
definicao
definicao

da
do
da
do
da

funcao quadrado
operador *
funcao quadrado
operador *
funcao fst

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:
fst (quadrado 5, quadrado 7)
= quadrado 5
-- usando a definicao da funcao fst
= 5 * 5
-- usando a definicao da funcao quadrado
= 25
-- usando a definicao do operador *
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 representao 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 exemplo, seja a definio da funo raizes a seguir,
raizes a b c = ((-b+d)/e, (-b-d)/e)
where d = sqrt (quadrado b - 4 * a * c)
e = 2 * a
onde o primeiro passo para a reduo da aplicao raizes 1 5 3 pode ser mostrado pelo grafo
da Figura 5.5.

((5 + )/ , (5 )/ )

sqrt (quadrado 5 4 * 1 * 3)

(2 * 1)

Figura 5.5: Representao grfica da funo raizes em ao.


Na realidade, existem mais 3 ponteiros, no mostrados na figura, que so os ponteiros para
as variveis a, b e c.
J sabemos que um operador lazy avalia uma operao apenas uma vez e se necessrio, ou seja,
no mximo uma vez. Isto tem influncia na construo de listas, principalmente no manuseio de
listas potencialmente infinitas. Para entender estas construes, necessrio compreender bem
como o avaliador funciona. Para isto, vamos comear analisando uma funo simples e depois
vamos mostrar exemplos mais complexos. Por exemplo, seja o sistema de avaliao da funo a
seguir:
eqFunc1 a b = a + b
eqFunc1 (9 - 3) (eqFunc1 34 (9 - 3))
= (9 - 3) + (eqFunc1 34 (9 - 3))
= 6 + (eqFunc1 34 6)
= 6 + (34 + 6)
= 6 + 40
= 46

--linha
--linha
--linha
--linha
--linha
--linha
--linha

1
2
3
4
5
6
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, leftmostoutermost. 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
eqFunc2 (10
= (10 *
= 400 +
= 432

= a + 32
* 40) (eqFunc1 34 3)
40) + 32
32

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.

5.6.1

Expresses ZF (revisadas)

Algumas aplicaes que usam a construo de listas por compreenso s podem ser completamente 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
pares :: [t] -> [u] -> [(t,u)]
pares l m = [(a,b) | a <- l, b <- m]
pares [1,2,3] [4,5] = [(1,4), (1,5), (2,4), (2,5), (3,4), (3,5)]
trianRet :: Int -> [(Int, Int, Int)]
trianRet n = [(a,b,c) | a <- [2..n], b <- [a+1..n], c <- [b+1..n],
a*a + b*b = c*c ]
triRetan 100 = [(3,4,5), (5,12,13), (6,8,10), ..., (65,72,97)]
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:
[a + b | a <- [1,2], isEven a, b <- [a..2*a]]
= [1 + b | isEven 1, b <- [1..2*1]] ++ [2 + b | isEven 2, b <- [2..2*2]]
= [1 + b | False, b <- [1..2*1]] ++ [2 + b | True, b <- [2..2*2]]
= [ ] ++ [2 + b |, b <- [2..2*2]]
= [2 + 2 |, 2 + 3 |, 2 + 4 | ]
= [2 + 2, 2 + 3, 2 + 4]
= [4, 5, 6]
[(a, b) |a <- [1..3], b <- [1..a]]
= [(1,b) | b <- [1..1] ++ [[(2,b) | b <- [1..2]] ++ [(3,b) | b <- [1..3]]
= [(1,1)|] ++ [(2,1)|] ++ [(2,2)|] ++ [(3,1)|] ++ [(3,2)|] ++ [(3,3)|]
= [(1,1), (2,1), (2,2), (3,1), (3,2), (3,3)]
Exerccio. Faa o clculo da expresso [a + b | a <- [1..4], b <- [2..4], a < b].

5.6.2

Dados sob demanda

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:
construir a lista [1..n],
elevar quarta potncia cada nmero da lista, gerando a lista [1, 16, ..., n4 ] e
encontrar a soma dos elementos desta lista: = 1 + 16 + ... + n4 .
Desta forma a funo somaQuartaPotencia pode ser definida da seguinte forma:
somaQuartaPotencia n = sum (map (^4) [1..n])
= sum (map (^4)(1 : [2..n]))
= sum (((^4) 1) : map(^4) [2..n])
154

=
=
=
=
=
=
=
=

sum
sum
1 +
1 +
1 +
1 +
1 +
1 +

((1^4) : map (^4) [2..n])


(1 : map (^4) [2..n])
sum (map (^4) [2..n])
sum ((^4)(2: [3..n])
sum (((^4) 2) : map (^4) [3..n])
sum ((2^4) : map (^4) [3..n]))
sum (16 : map (^4) [3..n])
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.

5.6.3

Listas potencialmente infinitas

Listas infinitas no tm sido utilizadas na maioria das linguagens de programao. Na realidade, as listas infinitas no podem ser implementadas em qualquer lingaugem de programao,
uma vez que a memria finita. O que realmente algumas linguagens implementam, incluindo
Haskell, so listas potencialmente infinitas. Mesmo nas linguagens funcionais, elas s podem ser
implementadas se a linguagem utilizar lazy evaluation. Vejamos um exemplo.
uns :: [Int]
uns = 1 : uns

-- a lista infinita de 1s [1, 1, ..]

somaPrimDoisUns :: [Int] -> Int


somaPrimDoisUns (a : b : x) = a + b
Seja agora a chamada a esta funo com o argumento uns.
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
1. A lista dos tringulos retngulos.
trigRet = [(a, b, c) | c < [2..], b < [2..c 1], a < [2..b 1], a a + b b = c c]
Verifique o que aconteceria se a ordem dos geradores fosse invertida!
2. O crivo de Eratstenes. O crivo de Eratstenes uma lista de inteiros criada a partir de
uma lista inicial. A lista inicial pode ser uma lista qualquer de inteiros. A partir dela,
o primeiro elemento desta lista far parte da nova lista que consiste deste elemento e do
crivo da lista que feita retirando-se os mltiplos deste valor. Assim, crivo definido da
seguinte forma:
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:
somaListas :: [Int] -> [Int]
somaListas lista = out
where out = 0 : zipWith (+) lista out
zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys
zipWit f _ _ = [ ]
Exerccio
Defina listas infinitas de fatorial e de Fibonacci.

5.7

Provas sobre tipos algbricos

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
data Arvore t = Nil | No t (Arvore t) (Arvore t)
deriving (Eq, Ord, Show)
Para provar uma propriedade P(tr) para todas as rvores finitas do tipo Arvore t, temos
de analisar dois casos:
1. o caso Nil: verificar se P(Nil) verdadeira e
2. o caso No: verificar se P(No x tr1 tr2) para todo x, assumindo P(tr1) e P(tr2).
Exemplo: provar que map f (collapse tr) = collapse (mapTree f tr).
Para provar isto devemos usar as seguintes definies:
map f [ ] = [ ]
map (a : x) = f a : map f x

(1)
(2)

mapTree f Nil = Nil


mapTree f (No x t1 t2 )
= No (f x) (mapTree f t1 ) (mapTree f t2 )

(3)

collapse Nil = [ ]
collapse (No x t1 t2 )
= collapse t1 ++ [x] ++ collapse t2

(4)
(5)
(6)

156

Esquema da prova:
O caso Nil:
Lado esquerdo
map f (collapse Nil)
= map f [ ]
=[]

por (5)
por (1)

Lado direito
collapse (mapTree f Nil)
= collapse Nil
=[]

por (3)
por (5)

Assim, a propriedade vlida para a rvore do tipo Nil.


O caso No:
Para o caso No, temos de provar que
map f (collapse (No x tr1 tr2)) = collapse (mapTree f (No x tr1 tr2))
assumindo que:
map f (collapse tr1) = collapse (mapTree f tr1) (7) e
map f (collapse tr2) = collapse (mapTree f tr2) (8)
usando ainda o fato de que map g (y ++ z) = map g y ++ map g z (9)
Lado esquerdo
map f (collapse(No x tr1 tr2))
=map f (collapse tr1 ++ [x] ++
collapse tr2)
=map f (collapse tr1) ++ [f x] ++
map f (collapse tr2)
=collapse (mapTree f tr1) ++ [f x]
++ collapse(mapTree f tr2)

por (6)
por (9)

Lado direito
collapse (mapTree f (No x tr1 tr2))
= collapse (No (f x)
(mapTree f tr1) (mapTree f tr2))

por (4)

=collapse (mapTree f tr1) ++ [f x]


++ collapse(mapTree f tr2)

por (6)

por (7 e 8)

Assim, a propriedade tambm vlida para a rvore do tipo No.


Concluso: a propriedade vlida para os casos Nil e No. Portanto, vlida para qualquer
rvore binria, ou seja,
map f (collapse tr) = collapse (mapTree f tr)
Exerccios:
1. Usando a definio de profundidade de uma rvore binria, feita anteriormente, prove
que, para toda rvore finita de inteiros tr, vale profundidade tr < 2(prof undidade tr) .
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

Modularizao e Tipos Abstratos de


Dados em Haskell
Our ability to decompose a problem into
parts depends directly on our ability to glue solutions together.
To assist modular programming a language must provide good glue.
Functional programming languages provide two new
kinds of glue - higher - order functions and lazy evaluation.
(John Hughes in [13])

6.1

Introduo

No Captulo introdutrio desta Apostila, afirmamos que a principal vantagem das linguagens estruturadas 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 programao devem apresentar para que sejam utilizadas com sucesso, na construo de grandes sistemas. 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:
as partes de um sistema podem ser construdas, compiladas e testadas separadamente e
pode-se construir grandes bibliotecas compostas de funes genricas, aumentando a reusabilidade.
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:
cada mdulo deve ter um papel claramente definido,
cada mdulo deve realizar exatamente uma nica tarefa,
159

cada mdulo deve ser auto-contido,


cada mdulo deve exportar apenas o que estritamente necessrio e
os mdulos devem ser pequenos.

6.2

Mdulos em Haskell

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

onde Formiga o mdulo, Formigas um tipo de dado e comeFormiga e textbfmataFormiga


so funes a serem utilizadas dentro do mdulo.
Os mdulos podem ser exportados e importados por outros mdulos ou programas. Estas
importaes e exportaes devem ter algum tipo de controle porque nem tudo deve ser exportvel
sem a aquiescncia do construtor do mdulo e o importador tambm deve ter o controle de
importar apenas o que ele acha necessrio.
Aps o nome do mdulo segue uma lista de componentes do mdulo, entre parnteses, e que
so visveis por outros mdulos e, portanto, podem ser exportados, seguidos pela palavra chave
where indicando que vai iniciar o corpo do mdulo. Isto permite que o cdigo possa ser mantido
escondido do mundo externo. A notao especial (..) que segue o nome Formigas indica que o
tanto o tipo quanto o construtor do tipo podem ser exportados. Na realidade, o nome do tipo
ou seja, o construtor do tipo, pode ser exportado mas, os construtores dos valores no podem.
Essa caracterstica importante porque permite esconder os detalhes da implementao de seus
usurios, tornando o tipo realmente abstrato.
Se os elementos a serem exportados, juntamente com os parnteses que os cercam, forem
omitidos na declarao do mdulo, todos os nomes no mdulo podem ser exportados. Uma
declarao deste tipo pode ser:
module ExportaTudo 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,
module ExportaTudo () where
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:
module Abelha (Formigas(..), pegadordeAbelha, comeFormiga) where . . .
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.

6.2.2

Importao de mdulos

Os mdulos, em Haskell, podem importar dados e funes de outros mdulos da seguinte forma:
module Abelha where
import Formiga
pegadordeAbelha = . . .
As definies visveis no mdulo Formiga podem ser utilizadas no mdulo Abelha.
module Vaca where
import Abelha
Neste caso, as definies do tipo Formigas e da funo comeFormiga no so visveis
em Vaca. Elas podem se tornar visveis pela importao explcita de Formiga ou usando os
controles de exportao para modificar o que exportado a partir de Abelha.

6.2.3

Controles de importao

Alm dos controles para exportao, Haskell tambm apresenta controles para a importao de
definies.
module Tall where
import Formiga (Formigas (. . .))
Neste caso, a inteno importar apenas o tipo Formigas do mdulo Formiga. Haskell
tambm prov uma forma explcita de esconder alguma entidade. Por exemplo,
module Tall where
import Formiga hiding (comeFormiga)
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:
import qualified Formiga
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
import Inseto as Formiga

6.3

Compilao de programas fontes usando GHC

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.

6.3.1

O mdulo Main

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:
module
import
import
import
main =

Main() where
Abelha
Formiga
Vaca
print (pegadordeAbelha.comeFormiga)

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.

6.4

Modularidade e abstrao de dados

At alguns tempos atrs, os programas de computador eram pequenos e ficaram conhecidos na


literatura como programming in the small. No entanto, a evoluo destas mquinas permitiram
e exigiram a construo de programas grandes (com alguns milhares de linhas de cdigos) que
foram alcunhados como programming in the large. Apesar de desejvel e at necessrio, este tipo
de construo de software trouxe consigo a necessidade de uma organizao eficaz e eficiente,
para que os programas pudessem ser mantidos de forma intelectualmente administrveis.
A soluo adotada foi organizar este programas em recipientes sintticos contendo grupos
de subprogramas e dados que estivessem logicamente relacionados, que ficaram conhecidos na
literatura como mdulos, um tema cuja implementao em Haskell foi objeto de estudo no
Captulo anterior.
Alm disso, a compilao de programas deixou de ser insignificante porque foi necessrio
encontrar uma forma de recompilar apenas as partes de um programa que sofressem alguma
alterao, sem ter que recompilar todo o programa. Os mdulos deveriam ter a possibilidade de
serem recompilados separadamente, ficando caracterizadas as unidades de compilao", encapsuladas em um agrupamento de subprogramas juntamente com os dados que eles manipulam.
Um tipo de dado abstrato um encapsulamento que inclui a represento do tipo de dado
em questo e as operaes sobre eles, ocultando os detalhes de implementao, permitindo que
sejam criadas instncias deste tipo, chamadas objetos. Desta forma, um objeto um tipo de
dado abstrato que satisfaz as duas condies seguintes:
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 representao 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 confiabilidade.
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.

6.5

Eficincia de programas funcionais

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 imperativos. 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 relativamente 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.

6.5.1

O desempenho da funo reverse

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
reverse :: [t] -> [t]
reverse [ ]
= [ ]
reverse (a : x) = reverse x ++ [a]
A despeito da simplicidade desta definio, ela padece de um srio problema de desempenho.
Verifiquemos quantos passos so necessrios para inverter uma lista l de n elementos, usando esta
definio. A funo chama a si mesma n vezes e, em cada uma destas chamadas, chama ++,
tail e head. As funes head e tail so primitivas, ou seja, exigem tempo constante para suas
execues. A operao ++ deve saltar para o final de seu primeiro argumento para concaten-lo
com o segundo. O tempo total de reverse dado pela soma

(n 1) + (n 2) + (n 3) + . . . + 1 + 0 =

n1
X
i=0

164

i=

n(n 1)
O(n2 )
2

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:
revaux :: [t] -> [t] -> [t]
revaux (a:la) ln = revaux la (a:ln)
revaux [ ] ln
= ln
A nova definio da funo reverse ser feita aplicando a funo revaux lista a ser invertida
e lista vazia como seus argumentos, ou seja:
reverse :: [t] -> [t]
reverse l = revaux l [ ]
A anlise da complexidade desta definio informa que ela proporcional ao tamanho da
lista, ou seja, O(n) [25, 34, 8]. Usando uma idia simples, foi possvel melhorar o desempenho
da definio original. A dificuldade foi descobrir esta idia. Estas idias sempre existem e a
programao em Haskell, ou em qualquer outra linguagem funcional, privilegia os programadores
que sejam capazes de encontr-las.
Vamos agora analisar o modelo de reduo de tempo e espao necessrios para avaliar expresses. Grande parte do material aqui descrito baseado no execelente livro de Richard Bird
[4].

6.6

Tipos abstratos de dados

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.

6.6.1

O tipo abstrato Pilha

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:
push
pop
top
stackEmpty
newStack

::
::
::
::
::

t -> Stack
Stack t ->
Stack t ->
Stack t ->
Stack t

t -> Stack t --coloca um item no topo


Stack t
--retira um item do topo
t
--pega o item do topo da
Bool
--verifica se a pilha eh
--cria uma pilha vazia

da pilha
da pilha
pilha
vazia

Sero feitas duas implementaes do tipo pilha, sendo a primeira baseada em tipos algbricos
e a segunda baseada em listas.
Primeira implementao para o TAD Pilha
A implementao de um tipo abstrato de dados em Haskell feita atravs da criao de um
mdulo, tema estudado na seo anterior. O leitor deve voltar a ele se tiver alguma dificuldade
de entender as declaraes aqui feitas. Vejamos a primeira implementao, baseada em tipos
algbricos.
module Stack(Stack, push, pop, top, stackEmpty, newStack) where
push
:: t -> Stack t -> Stack t
pop
:: Stack t -> Stack t
top
:: Stack t -> t
stackEmpty :: Stack t -> Bool
newStack
:: Stack t
data Stack t = EmptyStk
| Stk t (Stack t)
push x s = Stk x s
pop EmptyStk

= error "retirada em uma pilha vazia"

Apesar de preferirmos nomes em Portugus, nestes Exemplos, as operaes sero descritas com os nomes em
ingls por j fazerem parte do jargo da Computao.

166

pop (Stk _ s) = s
top EmptyStk = error "topo de uma pilha vazia"
top (Stk x _) = x
newStack = EmptyStk
stackEmpty EmptyStk = True
stackEmpty _
= False
instance (Show t) => Show (Stack t) where
show (EmptyStk) = "#"
show (Stk x s) = (show x) ++ "|" ++ (show s)
A utilizao do tipo abstrato Stack deve ser feita em outros mdulos cujas funes necessitam deste tipo de dado. Desta forma, o mdulo Stack deve constar na relao dos mdulos
importados pelo mdulo usurio. Por exemplo,
module Main where import Stack
listaParaPilha :: [t]
-> Stack t
listaParaPilha [ ]
= newStack
listaParaPilha (x : xs) = push x (listaParaPilha xs)
pilhaParaLista :: Stack t -> [t]
pilhaParaLista s
|stackEmpty s = [ ]
|otherwise
= (top s) : (pilhaParaLista (pop s))
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:
ex1 = push 14 (push 9 (push 19 (push 51 newStack)))
ex2 = push "Dunga" (push "Constantino")
A implementao mostrada foi baseada no tipo algbrico Stack t. No entanto, o implementador pode desenvolver uma outra implementao, mais eficiente que esta, e fazer a mudana
da implementao anterior para esta sem que os usurios precisem saber disto. Neste caso, as
funes do mdulo Main no precisam ser refeitas para serem utilizadas. O que no pode ser
modificada a interface com o usurio. Vamos construir a segunda implementao baseada em
listas finitas.
Segunda implementao para o TAD pilha
As operaes primitivas (assinatura) sero as mesmas da implementao anterior, ou seja, o que
muda apenas a implementao.
module Stack(Stack, push, pop, top, stackEmpty, newStack} where
push
:: t -> Stack t -> Stack t
pop
:: Stack t -> Stack t
top
:: Stack t -> t
stackEmpty :: Stack t -> Bool
167

newStack

:: Stack t

data Stack t = Stk [t]


push x (Stk xs) = Stk (x : xs)
pop (Stk [ ])
= error "retirada em pilha vazia"
pop (Stk (_ : xs)) = Stk xs
top (Stk [ ])
= error "topo de pilha vazia"
top (Stk (x : _)) = x
newStack = Stk [ ]
stackEmpty (Stk [ ]) = True
stackEmpty _
= False
instance (Show t) => Show (Stack t) where
show (Stk [ ]) = "#"
show (Stk (x : xs)) = (show x) ++ "|" ++ (show (Stk xs))
O mdulo Main o mesmo para as duas implementaes, no sendo necessrio ser mostrado
novamente.

6.6.2

O tipo abstrato de dado Fila

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 primitivas so necessrias para compor a assinatura do tipo abstrato Queue t. Neste caso, so
elas:
enqueue
dequeue
front
queueEmpty
newQueue

::
::
::
::
::

t -> Queue
Queue t ->
Queue t ->
Queue t ->
Queue t

t -> Queue t --coloca um item no fim da fila


Queue t
--retorna a fila sem o item da frente
t
--pega o item da frente da fila
Bool
--testa se a fila esta vazia
--cria uma nova fila vazia

Vamos continuar o exemplo provendo duas implementaes do tipo abstrato Fila, da mesma
forma feita para o tipo Pilha.
Primeira implementao para o TAD Fila
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
data Queue t = Fila [t]
enqueue x (Fila q) = Fila (q ++ [x])

--insere no final da lista

dequeue (Fila (x : xs)) = Fila xs


--retira o elemento da cabeca da lista
dequeue _
= error Fila de espera vazia
front (Fila (x : _)) = x
--o front eh o elemento a ser retirado
front _
= error "Fila de espera vazia"
queueEmpty (Fila [ ]) = True
queueEmpty _
= False
newQueue = (Fila [ ])
instance (Show t) => Show (Queue t) where
show (Fila [ ])
= ">"
show (Fila (x : xs)) = "<" ++ (show x) ++ (show (Fila xs))
Para utilizar o TAD Fila necessrio construir o mdulo Main, de forma similar a que foi
feita para a utilizao do TAD Pilha.
module Main where
import Stack
import Queue
filaParaPilha :: Queue t -> Stack t
--transforma uma fila em uma pilha
filaParaPilha q = qts q newStack
where qts q s
|queueEmpty q = s
|otherwise
= qts (dequeue q) (push (front q) s)
pilhaParaFila :: Stack t -> Queue t
--transforma uma pilha em uma fila
pilhaParaFila s = stq s newQueue
where stq s q
|stackEmpty s = q
|otherwise
= stq (pop s) (enqueue (top s) q)
inverteFila :: Queue t -> Queue t
inverteFila q = pilhaParaFila (filaParaPilha q)
invertePilha :: Stack t -> Stack t
invertePilha s = filaParaPilha (pilhaParaFila s)
Agora podemos tambm criar exemplos de filas da seguinte forma:
169

q1 = enqueue 14 (enqueue 9 (enqueue 19 newQueue))


Segunda implementao do TAD Fila
A segunda implementao do TAD Fila tambm baseada em listas finitas, mas leva em considerao 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:
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
data Queue t = Fila [t] [t]
newQueue = Fila [ ] [ ]
queueEmpty (Fila [ ] [ ]) = True
queueEmpty _
= False
front (Fila [ ] [ ]) = error "A fila esta vazia"
front (Fila (x:xs) _) = x
front (Fila [ ] ys)
= front (Fila (reverse ys) [ ])
enqueue y (Fila [ ] [ ] = Fila [y] [ ]
enqueue y (Fila _ ys) = Fila _ (y : ys)
dequeue (Fila (x : xs) ys)
dequeue (Fila [ ] [ ])
dequeue (Fila [ ] ys)

= Fila xs ys
= error "A fila esta vazia"
= Fila tail (reverse ys) [ ]

Esta implementao substancialmente mais eficiente que a implementao feita atravs de


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.

6.6.3

O tipo abstrato Set

O tipo abstrato Set uma coleo homognea de elementos e implementa a noo de conjunto,
de acordo com a seguinte Interface:
emptySet
setEmpty
inSet
addSet
delSet
pickSet

::
::
::
::
::
::

Set
Set
(Eq
(Eq
(Eq
Set

t
--cria um conjunto vazio
t -> Bool
--testa se o conjunto eh vazio
t) => t -> Set t -> Bool --testa se um x estah em S
t) => t -> Set t -> Set t --coloca um item no conjunto
t) => t -> Set t -> Set t --remove um item de um conjunto
t -> t
--seleciona um item de S

necessrio testar a igualdade entre os elementos de um conjunto. Por este motivo, os


elementos do conjunto tm de pertencer classe Eq. Existem algumas implementaes de
Set que exigem restries adicionais sobre os elementos do conjunto. Isto significa que pode-se
construir uma Interface mais rica para Set incluindo as operaes de unio, interseo e diferena
de conjuntos, no entanto estas operaes adicionais podem ser construdas a partir das operaes
definidas na Interface aqui mostrada.
module Set (Set, emptySet, setEmpty, inSet, addSet, delSet) where
emptySet
setEmpty
inSet
addSet
delSet
pickSet

::
::
::
::
::
::

Set
Set
(Eq
(Eq
(Eq
Set

t
t -> Bool
t) => t -> Set t -> Bool
t) => t -> Set t -> Set t
t) => t -> Set t -> Set t
t -> t

data Set t = S [t]

--listas sem repeticoes

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

6.6.4

O tipo abstrato Tabela

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:
module Table (Table, newTable, findTable, updateTable, removeTable) where
newTable
:: Table a b
findTable
:: (Ord a) => a -> Table a b -> Maybe b
updateTable :: (Ord a) => (a, b) -> Table a b -> Table a b
removeTable :: (Ord a) => a -> Table a b -> Table a b
data Table a b = Tab [(a,b)] --lista ordenada de forma crescente
newTable = Tab [ ]
findTable _ (Tab [ ]) = Nothing
findTable x (Tab ((c,v) : cvs))
|x < c
= Nothing
|x == c = Just v
|x > c
= findTable x (Tab cvs)
updateTable
updateTable
|x < c =
|x == c =
|x > c =
removeTable
removeTable
|x < c =
|x == c =
|x > c =

(x, z) (Tab [ ]) = Tab [(x, z)]


(x, z) (Tab ((c,v) : cvs))
Tab ((x,z):(c,v):cvs)
Tab ((c,z):cvs)
let (Tab t) = updateTable (x,z) (Tab cvs)
in Tab ((c,v):t)
_ (Tab [ ]) = Tab [ ]
x (Tab ((c,v):cvs))
Tab ((c,v):cvs)
Tab cvs
let (Tab t) = removeTable x (Tab cvs)
in Tab ((c,v):t)

instance (Show a, Show b) => Show (Table a b) where


show (Tab [ ]) = " "
show (Tab ((c,v):cvs))
= (show c)++"\t"++(show v)++"\n"++(show (Tab cvs))
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:
module Main where
import Table
172

type Numero = Integer


type Nome
= String
type Data
= Integer
pauta :: [(Numero, Nome, Data)] -> Table Numero (Nome, Data)
pauta [ ]
= newTable
pauta ((x,y,z):xyzs) = updateTable (x,(y,z)) (pauta xyzs)
teste = [(1111,"Dunga",14), (5555,"Constantino", 15),
(3333,"Afonso",18), (2222,"Cecilia",19), (7777,"Vieira",14),
(6666,"Margarida",26)]
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.

6.7.1

A criao de arrays

Os arrays, em Haskell, so criados atravs de trs funes pr-definidas: array, listArray e


accumArray, que sero descritas a seguir. A primeira delas, array, tem a seguinte sintaxe:
array <limites> <lista_de_associacoes>
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:
x
= array (1,5) [(3,n),(1,D),(2,u),(5,a),(4,g)]
fun n = array (0,n) [(i,3*i)|i<-[0..n]]
y
= array ((1,1),(2,2)) [((i,j),i*j)|i<-[1..2],j<-[1..2]]
O tipo de um array denotado por Array a b, onde a representa o tipo do ndice e b
representa o tipo do valor. Desta forma, os arrays anteriores tm os seguintes tipos:
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:
listArray <limites> <lista_de_valores>
onde o argumento <limites> tem o mesmo significado j visto para a funo array e o argumento <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:
x = listArray (1,5) "Dunga"
A terceira funo pr-definida, accumArray, tem a seguinte sintaxe:
accumArray <funcao> <init> <limites> <lista_de_associacoes>
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:
Array>accumArray (-) 0 (1,5) [ ]
array (1,5) [(1,0),(2,0),(3,0),(4,0),(5,0)]
Um exemplo mais completo, pode ser
Array>accumArray (+) 2 (1,5) [(1,2),(2,3),(1,3),(4,1)]
array (1,5) [(1,7),(2,5),(3,2),(4,3),(5,2)]
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.

6.7.2

Utilizando arrays

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)]
Tambm possvel atualizar um array em um estilo funcional, ou seja, a partir de um
array podemos retorna um novo array cujos elementos so os mesmos do array anterior, exceto
para alguns ndices determinados. Esta operao feita atravs do operador // que toma
como argumentos um array e uma lista de associaes e retorna um novo array onde a lista de
substituies passadas substitui as associaes do array antigo. Por exemplo, sendo x o array
definido acima, a chamada x // [(1,F)] ter como resultado "Funga".

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 desempenho 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 processados 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

Programao com aes em Haskell


... for exemple, a computation implying the modification of
a state for keeping track of the number of evaluating steps
might be described by a monad which takes as input parameter and
returns the new state as part of its result.
Computations raising exceptions or performing input-output can
also be described by monads.
(Fethi Rabhi et Guy Lapalme in [34])

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:
um programa pode necessitar ler ou escrever alguma entrada em algum terminal;
um sistema de e-mail l e escreve em arquivos ou em canais e
um programa pode querer mostrar uma figura em uma janela do monitor.
Historicamente, as operaes de I/O representaram um desafio muito grande durante muito
tempo para os usurios das linguagens funcionais. Os projetistas dessas linguagens tomaram
rumos distintos na soluo destes problemas. Por exemplo, os projetistas de Standard ML [30]
preferiram incluir operaes como
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 extremamente 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.

7.1.1

Ser ou no ser pura: eis a questo

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 tcnicas 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
m >>= (\x -> n)
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:
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
O estilo mondico rapidamente dominou os modelos anteriores. Os tipos so mais compactos
e mais informativos. Uma prova baseada em parametricidade garante que nenhuma referncia
desperdiada na mudana de uma computao encapsulada para outra [21]. O resultado prtico
disto foi que, pela primeira vez, esta prova ofereceu a possibilidade de se implementar uma funo
usando um algoritmo imperativo com a garantia da ausncia de qualquer side effect.
Em 1996, Peyton Jones e outros pesquisadores extenderam a mnada IO com threads, onde
cada thread pode realizar I/O, tornando a semntica da linguagem no determinstica [33]. Os
threads podem comunicar entre si usando locaes mutveis e sincronizadas chamadas MVars.
O leitor interessado em conhecer melhor este tema deve recorrer bibliografia especfica. Neste
particular, as referncias [48, 49] e [21] representam um bom comeo.

7.2

Entrada e Sada em Haskell

Como j descrito, um programa funcional consiste em uma expresso que avaliada para encontrar 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, cujos 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.

7.2.1

Operaes de entrada

A operao de leitura de um caractere (Char), a partir do dispositivo padro de entrada,


descrita em Haskell pela funo pr-definida getChar do tipo:
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.

7.2.2

Operaes de sada

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:
putStr :: String -> IO ()
Agora podemos escrever "Olha eu aqui!", da seguinte forma:
aloGalvao :: IO ()
aloGalvao = putStr "Olha eu aqui!"
Usando putStr podemos definir uma funo que escreva uma linha de sada:
putStrLn :: String -> IO ()
putStrLn = putStr . (++ "\n")
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
show :: Show a => a -> String
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
print :: Show a => a -> IO ()
print = putStrLn . show
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
cujo efeito no realizar qualquer ao de I/O e retornar um valor do tipo a.
180

7.2.3

O comando do

O comando do um mecanismo flexvel, construdo para realizar duas operaes sobre aes em
Haskell:
1. a seqencializao de aes de I/O e
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:
putStrLn :: String -> IO ()
putStrLn str = do putStr str
putStr "\n"
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:
faz4vezes :: String -> IO ()
faz4vezes str = do putStrLn str
putStrLn str
putStrLn str
putStrLn str
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.
fazNvezes :: Int -> String -> IO ()
fazNvezes n str = if n <= 1 then putStrLn str
else do putStrLn str
fazNvezes (n-1) str
Deve ser observada a recurso na cauda utilizada na definio da funo fazNvezes, simulando 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"
Capturando valores lidos
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
onde linha <- nomeia o resultado de getLine.
Apesar do identificador linha parecer com uma varivel em uma linguagem imperativa, seu
significado em Haskell bem diferente.
Exemplo. Seja a turma de uma disciplina escolar, onde os alunos tm 3 notas: n1, n2 e n3.
Vamos construir uma funo, em Haskell, que leia sequencialmente o nome de cada aluno e suas
3 notas e mostre o nome do aluno e sua mdia aritmtica. Este processo deve continuar at que
o nome do aluno seja Final.
Para isto, vamos construir a ao leiaAte da seguinte forma:
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)
A ao return ( ) representa uma ao que equivalente a fazer nada e retornar nada.

7.3

Arquivos, canais e descritores

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.
Tabela 7.1: Forma de operaes com
IOMode
l
escreve Posio inicial
ReadMode
Sim No
Incio do arquivo
WriteMode
No Sim
Incio do arquivo
ReadWriteMode Sim Sim
Incio do arquivo
AppendMode

No

Sim

Fim do arquivo

arquivos em Haskell.
Notas
O arquivo deve existir
Se existir o arquivo perde os dados
O arquivo criado se no existir
seno os dados so conservados
O arquivo criado se no existir e
os dados existentes so conservados

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 operacional 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:
data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode
openFile :: FilePath -> IOMode -> IO Handle
hClose :: Handle -> IO ()
Por conveno, todos os comandos usados para tratar descritores de arquivos so iniciadas
com a letra h. Por exemplo, as funes
hPutChar :: Handle -> Char -> IO ()
hPutStr :: Handle -> String -> IO ()
hPutStrLn :: Handle -> String -> IO ()
hPrint :: Show a => Handle -> a -> IO ()
so utilizadas para escrever alguma coisa em um arquivo. J os comandos
hGetChar :: Handle -> IO ()
hGetLine :: Handle -> IO ()
so utilizados nas operaes de leituras em arquivos. As funes hPutStrLn e hPrint incluem
um caractere \n para a mudana de linha ao final da string.
Haskell tambm permite que todo o contedo de um arquivo seja retornado como uma nica
string, atravs da funo
hGetContents :: Handle -> String
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
type FilePath = String
writeFile :: FilePath -> String -> IO ()
Tambm podemos acrescentar uma string ao final de um arquivo com o comando
appendFile :: FilePath -> String -> IO ()
Ento, qual a necessidade de se utilizar descritores? A resposta imediata: eficincia. Vamos
analisar.
Toda vez que o comando writeFile ou appendFile executado, deve acontecer tambm
uma seqncia de aes, ou seja, o arquivo deve ser inicialmente aberto, a string deve ser escrita
e, finalmente, o arquivo deve ser fechado. Se muitas operaes de escrita forem necessrias, ento
sero necessrias muitas destas seqncias. Ao se utilizar descritores, necessita-se apenas de uma
operao de abertura no incio e outra de fechamento no final.
Para ler ou escrever a partir de um descritor, que normalmente corresponde a um arquivo
em disco, o sistema operacional mantm um registro interno da posio atual de leitura do
arquivo, ou seja, um ponteiro para a posio atual. A cada vez que uma leitura feita, o sistema
operacional retorna o valor do dado apontado por este ponteiro e incrementa o ponteiro para
apontar para o prximo dado a ser lido.
O comando hTell pode ser utilizado para ver a posio corrente deste ponteiro em relao
ao incio do arquivo, que tem a posio 0 (zero). Isto significa que, quando um arquivo criado,
ele criado vazio e o ponteiro tem o valor 0 (zero). Quando se escrevem 10 bytes no arquivo, a
posio ser 10. hTell tem o tipo hTell :: Handle > IO Integer.
O complemento de hTell hSeek que permite trocar o valor da posio atual. Ele toma trs
parmetros: Handle, SeekMode e um endereo. O parmetro SeekMode pode ter trs valores
distintos para especificar como uma determinada posio deve ser interpretada. O primeiro valor
]textbfAbsoluteSeek indicando que a posio deve ser a atual que tambm a indicada pelo
valor de hTell. O segundo valor RelativeSeek que deve ser interpretado como um valor a
partir da posio atual, onde um valor positivo indica uma posio para a frente da posio
atual, um valor negativo indica uma posio para atrs da posio atual no arquivo. O terceiro
valor SeekFromEnd que especifica a quantidade de bytes antes do final do arquivo.
Vamos mostrar um exemplo baseado em OSullivan [29] que apresentado em vrias verses
para se verificar as possibilidades que Haskell oferece, atravs das aes.
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
loop_principal :: Handle -> Handle -> IO ()
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
processaDado :: String -> String
processaDado = map toUpper
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 associadas 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:
getChar = hGetChar stdin
putChar = hputChar stdout
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.

7.4

Gerenciamento de excees

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,
isEOFError :: IOError -> Bool
detecta o fim de um arquivo.
Mesmo assim, existe uma funo catch que faz o gerenciamento de excees. Seu primeiro
argumento a ao de I/O que se est tentando executar e seu segundo argumento um descritor
de excees, do tipo IOError > IO a. Vejamos:
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].
module Cadastro where
import IO
import System
import Numeric
import Char (toUpper)
--import Hugs,Prelude
--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)
f_menu :: Char -> IO ( )
f_menu i = do case i of
A -> insere_cadastro
B -> imprime_cadastros
C -> busca_p_nomes
D -> soma_d_idades
E -> media_d_alturas
F -> busca_p_sexo
G -> excluir_um_cadastro
H -> excluir_todos_cadastros
otherwise -> sair i
putStrLn "Operao concluda"
if not(i==I) then menu else putStr " "
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 . . ."
sair :: Char -> IO ( )
sair i
| i == I
= putStrLn "Saindo do sistema . . . "
|otherwise = putStrLn "Operao Invlida . . ."
-- FUNES AUXILIARES DE CONSULTA (134)
converteConteudo :: String -> IO [[String]]
converteConteudo conteudo = return (map (explodir #) (explodir \n conteudo))
-- FUNES COM NMEROS (139)
media_d_altura :: [[String]] -> Float
media_d_altura [ ] = 0.0
media_d_altura x = (soma_d_alturas x) /

(fromIntegral (length x))

soma_d_alturas :: [[String]] -> Float


soma_d_alturas [ ] = 0
soma_d_alturas (x:xs) = (read (altura x) :: Float) + (soma_d_alturas xs)
soma_d_idade :: [[String]] -> Integer
soma_d_idade [ ] = 0
soma_d_idade (x:xs) = (read (idade x) :: Integer) + (soma_d_idade xs)
-- FUNES AUXILIARES (153)
explodir :: Eq a => a -> [a] -> [[a]]
explodir a [ ] = [ ]
explodir a (x : xs)
| (takeWhile (/=a) (x : xs)) == [ ] = explodir a xs
| x == a = (takeWhile (/= a) xs) : explodir a (dropWhile (/= a) xs)
| otherwise = (takeWhile (/= a) (x : xs)) : explodir a (dropWhile (/= a) (x : xs))
nome, idade, altura, sexo :: [String] -> String

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
copiar origem destino =
do pt_arq <-abreArquivo origem ReadMode
conteudo <- (hGetContents pt_arq)
aux_pt_arq <- abreArquivo destino WriteMode
hPutStr aux_pt_arq conteudo
fechaArquivo aux_pt_arq
fechaArquivo pt_arq
-- FUNES AUXILIARES DE BUSCA (176)
busca_p_algo :: ([[String]] -> a -> IO b) -> a -> IO ( )
busca_p_algo funcao filtro =
do putStrLn " "
putStrLn "-----------------------------------------"
pt_arq <- abreArquivo "dados.txt" ReadMode
conteudo <- (hGetContents pt_arq)
cadastros <- (converteConteudo (conteudo))
funcao cadastros filtro
fechaArquivo pt_arq
putStrLn "-----------------------------------------"
busca_por_nome
busca_por_nome
busca_por_nome
|(nome x)

:: [[String]] -> String -> IO ( )


[ ] nm
= putStrLn " "
(x : xs) nm
== nm = do putStrLn (foldl1 (\a b->a++" "++b) x)
busca_por_nome xs nm
|otherwise
= busca_por_nome xs nm

busca_por_sexo
busca_por_sexo
busca_por_sexo
| (sexo x)

:: [[String]] -> Char -> IO ( )


[ ] sx
= putStrLn " "
(x : xs) sx
== (""++ [(toUpper sx)]++"") = do putStrLn (foldl1 (\a b->a++"
busca_por_sexo xs sx
| otherwise = busca_por_sexo xs sx

--FUNES AUXILIARES DE IMPRESSO (202)


imprime ::[[[Char]]] -> IO ( )
imprime [ ] = putStrLn " "
imprime (x : xs) = do putStrLn (foldl1 (\a b->a++"
imprime xs
--FUNO DE INCLUSO
apaga_p_nome :: [[String]] -> String -> String

191

"++b) x)

"++b) x)

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)
--FUNES AUXILIARES DE ARQUIVOS
--modo: AppenMode, WriteMode, ReadMode
abreArquivo :: String -> IOMode -> IO Handle
abreArquivo arquivo modo =
catch (openFile arquivo modo)
(\_ -> do {
putStrLn ("Impossvel abrir "++ arquivo);
putStrLn "Ser aberto com um nome defaul: dados.txt e limpo";
pt_arq <- abreArquivo "dados.txt" WriteMode;
fechaArquivo pt_arq;
abreArquivo "dados.txt" ReadMode
}
)
fechaArquivo :: Handle -> IO ( )
fechaArquivo handle_arq = hClose handle_arq

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 desenvolver 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.
[6] BRAINERD, W. S. et LANDWEBER, L. H. Theory of Computation. John Wiley & Sons,
1974.
[7] CURRY, H. B. et FEYS, R. and CRAIG, W. Combinatory Logic. Volume I; North Holland,
1958.
[8] DAVIE, Antony J. T. An Introduction to Functional Programming Systems Using Haskell.
Cambridge Computer Science Texts. Cambridge University Press. 1999.
[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.
[14] HUGHES,
John.
The
Design
of
http://citeseer.ist.psu.edu/hughes95design.html. 1995.

Pretty-printing

Library.

[15] HUTTON, Graham. newblock Programming in Haskell. newblock Cambridge University


Press. ISBN 978-0-521-69269-4. 2007.
[16] JOHNSSON, T. Efficient Computation of Lazy Evaluation. Proc. SIGPLAN84. Symposium on Compiler Construction ACM. Montreal, 1984.
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.
[20] LANDIN, P. J. The Mechanical Evaluation of Expressions. Computer Journal, Vol. 6, 4.
1964.
[21] LAUNCHBURY, J et PEYTON JONES, S. L. State in Haskell. Lisp and Symbolic
Computation, 8(4):293-342. 1995.
[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.
[25] MACLENNAN, Bruce J. Functional Programming Practice. Addison-Wesley Publishing
Company, Inc. 1990.
[26] MEIRA, Slvio Romero de Lemos. Introduo Programao Funcional. VI Escola de
Computao. Campinas, 1988.
[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.
OReilly. 2008.

Real World Haskell.

[30] PAULSON, Laurence C. ML for the Working Programmer. Cambridge University Press,
1991.
[31] PEMMARAJU, Sriram et SKIENA, Steven. Computational Discrete Mathematics: Combinatorics and Graph Theory with Mathematica. Cambridge University Press. 2003.
[32] PEYTON JONES, S. L. The Implementation of Functional Programming Languages. C.
A. R. Hoare Series Editor. Prentice/Hall International. 1987.
[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.
[34] RABHI, Fethi et LAPALME, Guy. Algorithms: A Functional Programming Approach.
2nd. edition. Addison-Wesley. 1999.
[35] ROJEMO, Niklaus. Garbage Collection and Memory Efficiency in Lazy Functional Languages. 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.
[39] SKIENA, Steven S. et REVILLA, Miguel A. Programming Challenges: The Programming
Context Training Manual. Texts in Computer Science. Springer Science+Business Media,
Inc. 2003.
[40] SOUZA, Francisco Vieira de. Gerenciamento de Memria em CMC. Dissertao de
Mestrado. CIn-UFPE. Maro de 1994.
[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 Espaotemporal 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 Functional 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.
[45] STOY, J. E. Denotational Semantics: The Scott-Strachey Approach to Programming Language Theory. MIT Press, 1977.
[46] THOMPSON, Simon. Haskell: The Craft of Functional Programming. 2nd. Edition. Addison Wesley. 1999.
[47] TURNER, David A. A New Implementation Technique for Applicative Languages. Software
Practice and Experience. Vol. 9. 1979.
[48] WADLER, Philip. Comprehendig Monads. Mathematical Structures in Computer Science;
2:461-493, 1992.
[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

Algumas funes padres


Neste Apndice esto listadas as definies de algumas funes corriqueiramente utilizadas na
programao funcional em Haskell. As definies so baseadas em Richard Bird [4], mas podem
apresentar algumas deferenas tanto em relao s definies apresentadas no Prelude, quanto
em relao ao conjunto de definies apresentado pelo autor citado, uma vez que preferimos
evitar o uso de letras gregas para representar tipos polimrficos.
1. (.). Composio funcional:
(.) :: (t -> u) -> (v -> t) -> (v -> u)
(f.g) x = f (g x)
2. (++). Concatenao de duas listas:
(++) :: [t] -> [t] -> [t]
[ ] ++ ys
= ys
(x : xs) ++ ys = x : (xs ++ ys)
3. (&&). Conjuno:
(&&) :: Bool -> Bool -> Bool
True && x = x
False && x = False
4. (||). Disjuno:
(||) :: Bool -> Bool -> Bool
True || x = True
False || x = x
5. (!!). Indexao de listas:
(!!) :: [t] -> Int
[ ] !! n
=
(x : xs) !! 0
=
(x : xs) (n + 1) =

-> t
error "(!!): index too large"
x
xs !! n
197

6. and. Retorna a conjuno lgica de uma lista de booleanos:


and :: [Bool] -> Bool
and = foldr (&&) True
7. concat. Concatenao de uma lista de listas:
concat :: [[t]] -> [t]
concat = foldr (++) [ ]
8. const. Cria uma funo de valor constante:
const :: t -> u -> t
const x y = x
9. cross. Aplica um par de funes a elementos correspondentes do par:
cross :: (t -> u, v -> w) -> (t, v) -> (u, w)
cross (f, g) = pair (f . fst, g . snd)
10. curry. Converte uma funo no currificada em uma currificada:
curry :: ((t, u) -> w) -> (t -> u -> w)
curry f x y = f (x, y)
11. drop. Seleciona a cauda de uma lista:
drop
drop
drop
drop

:: Int -> [t] -> [t]


0 xs
= xs
(n + 1) [ ]
= [ ]
(n + 1) (x :xs) = drop n xs

12. dropWhile. Remove o segmento inicial de uma lista se os elementos do segmento satisfizerem uma determinada propriedade:
dropWhile :: (t -> Bool) -> [t] -> [t]
dropWhile p [ ]
= [ ]
dropWhile p (x : xs) = if p x then dropWhile p xs else x : xs
13. filter. Seleciona os elementos de uma lista que apresentam uma determinada propriedade:
filter :: (t -> Bool) -> [t] -> [t]
filter p [ ]
= [ ]
filter p (x : xs) = if p x then x : filter p xs else filter p xs
14. flip. Inverte o posicionamento dos argumentos de uma funo:
flip :: (u -> t -> v) -> t -> u -> v
flip f x y = f y x
198

15. foldl. Aplica uma funo entre os elementos de uma lista pela esquerda:
foldl :: (u -> t -> u) -> u -> [t] -> u
foldl f e [ ]
= e
foldl f e (x : xs) = strict (foldl f) (f e x) xs
16. foldl1. Aplica uma funo entre elementos de listas no vazias, pela esquerda:
foldl1 :: (t -> t -> t) -> [t] -> t
foldl1 f [ ]
= error "foldl1 : empty list"
foldl1 f (x : xs) = foldl f x xs
17. foldr. Aplica uma funo entre os elementos de uma lista pela direita:
foldr :: (t -> u -> u) -> u -> [t] -> u
foldr f e [ ]
= e
foldr f e (x : xs) = f x (foldr f e xs)
18. foldr1. Aplica uma funo entre elementos de listas no vazias, pela direita:
foldr1
foldr1
foldr1
foldr1

:: (t -> t
f [ ]
f [x]
f (x : xs)

-> t) -> [t] -> t


= error "foldr1: empty list"
= x
= f x (foldr1 f xs)

19. fst. Seleciona o primeiro elemento de um par:


fst :: (t, u) -> t
fst (x, y) = x
20. head. Retorna a cabea de uma lista no vazia:
head :: [t] -> t
head [ ]
= error "head: empty-list"
head (x : xs) = x
21. id. Retorna a funo identidade:
id :: t -> t
id x = x
22. init. Retorna uma lista sem o seu ltimo elemento:
init
init
init
init

:: [t] -> [t]


[ ]
= error "init: empty list"
[x]
= [ ]
(x : y : xs) = x : init (y : xs)

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)
24. last. Retorna o ltimo elemento de uma lista no vazia:
last
last
last
last

:: [t] -> t
[ ]
= error "last: empty list"
[x]
= x
(x : y : xs) = last (y : xs)

25. length. Retorna a quantidade de elementos de uma lista:


length : [t] -> Int
length [ ]
= 0
length (x :xs) = 1 + length xs
26. map. Aplica uma funo a todos os elementos de uma lista:
map :: (t -> u) -> [t] -> [u]
map f [ ]
= [ ]
map f (x : xs) = f x : map f xs
27. not. A funo de negao de um valor booleano:
not :: Bool -> Bool
not True = False
not False = True
28. null. Verifica se uma lista , ou no, vazia:
null :: [t] -> Bool
null [ ]
= True
null (x : xs) = False
29. or. Retorna a disjuno lgica entre os elementos de uma lista:
or :: [Bool] -> Bool
or = foldr (||) false
30. pair. Aplica um par de funes a um argumento:
pair :: (t -> u, t -> v) -> t -> (u, v)
pair (f, g) x = (f x, g x)
31. partition. Particiona uma lista de acordo com um dado teste:
partition :: (t -> Bool) -> [t] -> ([t], [t])
partition p [ ] = ([ ], [ ])
partition p (x : xs) = if p x then (x : ys, zs) else (ys, x : zs)
where (ys, zs) = partition p xs
200

32. reverse. Inverte os elementos de uma lista finita:


reverse :: [t] -> [t]
reverse = foldl (flip (:)) [ ]
33. scanl. Aplica foldl a um segmento inicial no vazio de uma lista no vazia:
scanl :: (u -> t -> u) -> u
scanl f e xs = e : scanl f
where scanl
scanl

-> [t] -> [u]


e xs
f a [ ]
= [ ]
f a (y : ys) = scanl f (f a y) ys

34. scanl1. Aplica foldl1 a todo o segmento inicial no vazio de uma lista no vazia:
scanl1 :: (t -> t -> t) -> [t] -> [t]
scanl1 f [ ]
= error "scanl1: empty list"
scanl1 f (x : xs) = scanl f x xs
35. scanr. Aplica foldr ao segmento cauda de uma lista:
scanr :: (t -> u -> u) -> u -> [t] -> [u]
scanr f e [ ]
= [e]
scanr f e (x : xs) = f x (head ys) : ys
where ys = scanr f e xs
36. scanr1. Aplica foldr1 a cauda no vazia de uma lista:
scanr1
scanr1
scanr1
scanr1

:: (t -> t -> t)
f [ ]
=
f [x]
=
f (x : y : xs) =

-> [t] -> [t]


error "scanr1: empty list"
[x]
f x (head zs)
where zs = scanr1 f (y : xs)

37. singleton. Verifica se uma lista tem apenas um elemento:


singleton :: [t] -> Bool
singleton xs = (not (null xs)) && null (tail xs)
38. span. Divide uma lista em duas partes:
span :: (t -> Bool) -> [t] -> ([t], [t])
span p [ ]
= ([ ], [ ])
span p (x : xs) = if p x then (x : ys, zs) else ([ ], x : xs)
where (ys, zs) = span p xs
39. splitAt. Divide uma lista em duas partes de determinados tamanhos:
splitAt
splitAt
splitAt
splitAt

:: Int -> [t] ->


0 xs
(n + 1) [ ]
(n + 1) (x : xs)

([t], [t])
= ([ ], xs)
= ([ ], [ ])
= (x : ys, zs)
where (ys, zs) = splitAt n xs
201

40. snd. Seleciona o segundo componente de um par:


snd :: (t, u) -> u
snd (x, y) = y
41. tail. Remove o primeiro elemento de uma lista no vazia:
tail :: [t] -> [t]
tail [ ]
= error "tail: empty list"
tail (x : xs) = xs
42. take. Seleciona um segmento inicial de uma lista:
take
take
take
take

:: Int -> [t] ->


0 xs
(n + 1) [ ]
(n + 1) (x : xs)

[t]
= [ ]
= [ ]
= x : take n xs

43. takeWhile. Seleciona o segmento inicial dos elementos de uma lista que satisfazem um
dado predicado:
takeWhile :: (t -> Bool) -> [t] -> [t]
takeWhile p [ ]
= [ ]
takeWhile p (x : xs) = if p x then x : takeWhile p xs else [ ]
44. uncurry. Converte uma funo currificada em uma verso no currificada:
uncurry :: (t -> u -> v) -> (t, u) -> v
uncurry f xy = f (fst xy) (snd xy)
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:
until :: (t -> Bool) -> (t -> t) -> t -> t
until p f x = if p x then x else until p f (f x)
46. unzip. Transforma uma lista de pares em um par de listas:
unzip :: [(t, u)] -> ([t], [u])
unzip = foldr f ([ ], [ ])
where f (x, y) = cross ((x : ), (y : ))
47. wrap. Converte um valor em uma lista de um nico elemento:
wrap :: t -> [t]
wrap x = [x]
48. zip. Transforma um par de listas em uma lista de pares:
202

zip
zip
zip
zip

:: [t] -> [u] -> [(t, u)]


[ ] ys
= [ ]
(x : xs) [ ]
= [ ]
(x : xs) (y : ys) = (x, y) : zip xs ys

49. zipp. A verso no currificada da funo zip:


zipp :: ([t], [u]) -> [(t, u)]
zipp = uncurry zip

203

204

Apndice B

Compilao e execuo de programas


em Haskell
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 executveis permitem a utilizao de ferramentas importantes na depurao e otimizao de programas. 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.

B.2

Baixando e instalando o GHC

O compilador GHC pode ser instalado em vrias plataformas mas, neste Apndice, ser analisada 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 ghc6.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 existncia da funo main() da mesma forma. Por exemplo, para compilar um programa simples
primeiro.hs, cujo contedo o seguinte,
main = putStr "Primeiro programa executavel em Haskell"
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:
ghc -o primeiro primeiro.o
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.

B.3.1

Passando parmetros para um programa executvel

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.

B.3.2

Diretivas de compilao

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, dependendo das diretivas utilizadas. Entre essas diretivas, algumas podem ser citadas:
-c : o preprocessador gera um arquivo de extenso .o que o cdigo objeto e para a compilao;
-C : o preprocessador gera um arquivo de extenso .hc que o programa em C e para a
compilao;
-S : o preprocessador gera um arquivo de extenso .s que o programa em Assembly e para
a compilao;
-O : o preprocessador usa um pacote otimizador de cdigo rpido;
-prof compila cdigo para o profile mostrar funes de centro de custo;
-H14m : aumenta o tamanho do heap de execuo para executar mais rpido;
Como exemplos de chamadas de compilao podem ser mostrados os seguintes:
ghc -c -O Primeiro.hs. Compila o mdulo Primeiro.hs e gera o arquivo Primeiro.o
com cdigo otimizado.
ghc -o primeiro Primeiro.o.
primeiro.exe.

Compila o arquivo Primeiro.o e gera o executvel

ghc -C -H16m Primeiro.hs. Compila um mdulo em Haskell para C (o arquivo Primeiro.hc)


com uma heap maior.
ghc -S Primeiro.hc. Compila o arquivo C (compilado para C) para o arquivo Primeiro.s.
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

Você também pode gostar