Intro Ocaml
Intro Ocaml
Intro Ocaml
em OCaml
M ario Pereira and Sim ao Melo de Sousa
RELEASE - RELiablE And SEcure computation Group
LIACC & Departamento de Informatica
Universidade da Beira Interior
Documento de Trabalho. Versao de 26 de Fevereiro de 2012
Conte udo
1 Introducao 4
1.1 Heranca ML . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2 Compilacao, Execucao e Bibliotecas 7
2.1 Execuc ao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2 Compilac ao . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.3 Acesso ` as bibliotecas . . . . . . . . . . . . . . . . . . . . . . . 9
2.4 Ambiente de Programacao . . . . . . . . . . . . . . . . . . . . 10
3 N ucleo funcional 11
3.1 Linguagens fortemente tipicadas e Polimorsmo . . . . . . . 12
3.1.1 Convers oes explcitas . . . . . . . . . . . . . . . . . . . 13
3.2 Vari aveis e func oes . . . . . . . . . . . . . . . . . . . . . . . . 14
3.3 Valores, funcoes e tipos de base . . . . . . . . . . . . . . . . . 15
3.3.1 Valores Numericos . . . . . . . . . . . . . . . . . . . . 15
3.3.2 Caracteres e strings . . . . . . . . . . . . . . . . . . . . 17
3.3.3 Booleanos . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.3.4 Tipo unidade . . . . . . . . . . . . . . . . . . . . . . . 17
3.3.5 Produto Cartesiano, tuplos . . . . . . . . . . . . . . . . 18
1
3.3.6 Listas . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.4 Estruturas Condicionais . . . . . . . . . . . . . . . . . . . . . 20
3.5 Declarac ao de valores . . . . . . . . . . . . . . . . . . . . . . . 20
3.5.1 Declara coes globais . . . . . . . . . . . . . . . . . . . . 21
3.5.2 Declara coes locais . . . . . . . . . . . . . . . . . . . . . 22
3.6 Express oes funcionais e func oes . . . . . . . . . . . . . . . . . 22
3.6.1 Declara cao de base . . . . . . . . . . . . . . . . . . . . 22
3.6.2 Sintaxe alternativa para func oes n- arias . . . . . . . . . 25
3.6.3 Declara coes de valores funcionais . . . . . . . . . . . . 25
3.6.4 Declara cao de func oes locais . . . . . . . . . . . . . . . 26
3.6.5 Func oes de ordem superior . . . . . . . . . . . . . . . . 26
3.6.6 Fecho . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.6.7 Ordem Superior e Polimorsmo . . . . . . . . . . . . . 33
3.7 Uma digress ao sobre a noc ao de igualdade . . . . . . . . . . . 34
3.8 Declarac ao de tipos e ltragem de motivos . . . . . . . . . . . 35
3.8.1 Pattern matching (ltragem) . . . . . . . . . . . . . . . 35
3.8.2 Declara cao de tipos . . . . . . . . . . . . . . . . . . . 37
3.8.3 Registos . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.8.4 Tipos soma . . . . . . . . . . . . . . . . . . . . . . . . 39
3.9 Excepc oes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.9.1 Denic ao . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.9.2 Lan car uma excep cao . . . . . . . . . . . . . . . . . . . 41
3.9.3 Recuperar excepc oes . . . . . . . . . . . . . . . . . . . 41
4 Programacao imperativa 42
4.1 Estruturas de Dados modic aveis . . . . . . . . . . . . . . . . 42
4.1.1 Vectores . . . . . . . . . . . . . . . . . . . . . . . . . . 42
4.1.2 Strings como vectores . . . . . . . . . . . . . . . . . . . 44
4.1.3 Registos e campos modic aveis . . . . . . . . . . . . . 44
4.1.4 Referencias . . . . . . . . . . . . . . . . . . . . . . . . 45
4.2 Entradas e sadas . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.2.1 Abertura de cheiro . . . . . . . . . . . . . . . . . . . 47
4.2.2 Fecho de cheiro . . . . . . . . . . . . . . . . . . . . . 47
4.2.3 Leitura e Escrita . . . . . . . . . . . . . . . . . . . . . 47
4.3 Estruturas de controlo . . . . . . . . . . . . . . . . . . . . . . 48
4.3.1 Sequencia . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.3.2 Blocos de programa . . . . . . . . . . . . . . . . . . . . 49
4.3.3 Ciclos . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
2
4.3.4 Tipagem, aplicacao parcial e fechos . . . . . . . . . . . 50
5 Avaliacao Ansiosa, Avaliacao preguicosa e estruturas de da-
dos innitas 53
5.1 Avaliac ao em OCaml . . . . . . . . . . . . . . . . . . . . . . . 54
5.2 Avaliac ao Preguicosa em OCaml . . . . . . . . . . . . . . . . 56
5.3 O M odulo Lazy . . . . . . . . . . . . . . . . . . . . . . . . . . 61
5.4 Streams: uxos ou listas innitas em OCaml . . . . . . . . . 63
5.4.1 Construc ao . . . . . . . . . . . . . . . . . . . . . . . . 64
5.4.2 Consumo e percurso de uxos . . . . . . . . . . . . . . 65
5.4.3 Filtragem destrutiva de uxos . . . . . . . . . . . . . . 66
6 M odulos 69
6.1 Ficheiros e modulos . . . . . . . . . . . . . . . . . . . . . . . . 70
6.2 Encapsulamento . . . . . . . . . . . . . . . . . . . . . . . . . . 71
6.2.1 Compilac ao separada . . . . . . . . . . . . . . . . . . . 73
6.2.2 Linguagem de m odulos . . . . . . . . . . . . . . . . . . 74
6.3 Functores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
6.3.1 Aplicac oes . . . . . . . . . . . . . . . . . . . . . . . . . 80
7 Persistencia 81
7.1 Estruturas de dados imut aveis . . . . . . . . . . . . . . . . . . 81
7.2 Interesses pr aticos da persistencia . . . . . . . . . . . . . . . . 86
7.3 Interface e persistencia . . . . . . . . . . . . . . . . . . . . . . 90
8 Exemplos 94
8.1 Produto Cartesiano . . . . . . . . . . . . . . . . . . . . . . . . 94
8.2 Brincadeiras a volta da sequencia de Fibonacci . . . . . . . . . 95
8.3 Exercitar a estrategia dividir para conquistar . . . . . . . . . . 100
8.4 Polin omios, e o Metodo de Horner . . . . . . . . . . . . . . . . 101
8.5 Matrizes e cheiros . . . . . . . . . . . . . . . . . . . . . . . . 102
8.6 Memoiza cao automatica . . . . . . . . . . . . . . . . . . . . . 107
8.7 Tipo Soma e Induc ao Estrutural . . . . . . . . . . . . . . . . . 110
8.8 Execuc ao de automatos . . . . . . . . . . . . . . . . . . . . . . 119
8.9 Parsing com uxos e computac ao simb olica . . . . . . . . . . . 124
3
Aviso Previo
A redaccao deste documento baseou-se fortemente na bibliograa indi-
cada. Parece-nos entao obvio que a leitura e a aprendizagem directa
pelas obras originais e recomendada, e mesmo essencial ` a compreens ao
profunda das noc oes aqui apresentadas;
O portugues n ao e a lngua materna do principal autor e o presente
documento encontra-se em fase de elabora cao pelo que se agradece e
ate se incentiva qualquer sugestao ou correcc ao.
Bibliograa
Referencias principais usadas neste documento e das quais se aconselha
leitura s ao [4, 6, 2] (Com a autorizac ao concedida gentilmente por Jean-
Christophe Filliatre e por Xavier Leroy).
As referencias secundaria utilizadas (por consultar igualmente) s ao [7, 3, 9,
8, 5, 10]
1 Introducao
A linguagem LISP, criada no M.I.T. nos anos 1960, e o patriarca das lin-
guagens funcionais. Muitas variantes desta linguagem apareceram, como
o ELISP que e a linguagem de programac ao associada ao editor de texto
Emacs. Outro exemplo e a linguagem Scheme que est a, por exemplo, na
base do sistema de processamento de imagens GIMP.
CAML e o acr onimo para Categorical Abstract Machine Language que
sublinha as ligacoes que esta linguagem alimenta com o calculo e a teoria
das categorias. A CAM (de Categorical Abstract Machine) e uma maquina
abstracta (diz-se tambem maquina virtual) capaz de denir e de executar
funcoes. A outra liac ao do nome OCaml e a linguagem ML (de Meta Lan-
guage). As linguagens da famlia ML foram desenvolvidas no incio dos anos
1980. Na origem esteve a linguagem desenvolvida por Robert Milner em 1978.
Da descendencia destacamos os tres dialectos principais que s ao o SML-NJ
(New-Jersey, USA) , Moscow-ML (R ussia) e o OCaml (Franca). Um ramo
dissidente desta descendencia mas importante e a famlia das linguagens
4
qualicadas de preguicosas
1
como o Haskell, que s ao capazes naturalmente
de trabalhar com tipos de dados de tamanho innito.
O primeiro compilador Caml produzindo c odigo para a m aquina CAM
foi escrito em 1984. Actualmente o compilador OCaml produz c odigo para
uma m aquina virtual diferente e mais eciente, chamada ZINC e escrita pelo
Xavier Leroy. Desde ent ao a linguagem OCaml tem conseguido superar um
desao que poucas linguagens conseguiram: aliar uma boa fundamentac ao
te orica, sinonima de robustez e abilidade, ` a ec acia do c odigo produzido.
Um argumento, subjectivo, e por exemplo o comparativo de linguagens de-
senvolvido em [1] em que a linguagem Caml ca imediatamente atras da
linguagem C. Se a linguagem OCaml e uma linguagem popular nos meios
academicos
2
e no mundo da investigac ao, e tambem uma linguagem utili-
zada em ambiente industrial. Citemos por exemplo o Ocaml Consortium
3
que testemunha tal interesse pelos industriais, a ind ustria das comunica coes
(como a France Telecom), a industria aeron autica (Dassault,...), a nanca
(Janes Street Capital, NY) ou ate mesmo Microsoft (n ao so para a sua divi-
s ao Research, mas tambem desenvolvimento).
1.1 Heranca ML
A linguagem OCaml, como todas as linguagens herdeiras de ML, possu as
caractersticas seguintes:
As fun coes s ao valores deprimeira classe: estas podem ser argumentos
de outras func oes ou mesmo o resultado de um c alculo. Neste sentido
diz-se da linguagem OCaml que e uma linguagem de func oes de ordem
superior.
A linguagem e fortemente tipicada: a qualquer valor esta associado um
tipo. A vericac ao sistem atica e rigorosa da compatibilidade entre os
tipos dos par ametros formais e os tipos dos par ametros efectivos e uma
das caractersticas da linguagem. Tal permite eliminar grande parte
dos erros introduzidos por distracc ao e contribu para uma execucao
segura, avel e robusta.
1
Pregui cosas, no bom sentido: so executam certas tarefas quando estritamente neces-
sarias.
2
O Caml-light e por exemplo uma variante Caml particularmente adaptada `a aprendi-
zagem da programacao.
3
http://caml.inria.fr/consortium/.
5
A tipagem e inferida automaticamente: o sistema de tipo de OCaml e
particularmente expressivo e o problema da inferencia de tipo continua
no entanto decidvel. Isto e, o programador nao tem de declarar o tipo
das express oes que dene.
E uma opc ao. O sistema de tipos subjacente
` a linguagem OCaml e sucientemente poderoso para permitir que se
deduza esta informac ao a partir da sintaxe da express ao considerada.
A tipagem e est atica: todos os tipos podem ser (e s ao) deduzidos na fase
da compilac ao. Desta forma tal vericacao n ao e necess aria durante a
execu cao tornando essa ultima mais r apida.
A tipagem e polimorca: o algoritmo de inferencia de tipo do OCaml
sabe reconhecer tipos polim orcos. Voltaremos a este assunto seccao
precedente, mas podemos desde j a realcar que tal caracterstica per-
mite, de forma elegante, uma programac ao modular e generica.
Mecanismos de denicao e de gest ao de excepc oes: permite uma pro-
gramac ao capaz de interferir com uxo de controlo (nome generico para
denir o decorrer de uma execuc ao).
Mecanismos de ltragem de motivos (concordancia de padr ao): este
mecanismo possibilita uma programacao, muito conveniente, baseada
na analise dos casos possveis.
A mem oria e gerida por um recuperador automatico de memoria (Gar-
bage Colector ou GC, em ingles): n ao h a gest ao explcita da mem oria.
O GC trata de tudo automaticamente e de forma eciente.
Alem dessas caractersticas, OCaml:
disp oe de mecanismos proprios `a programac ao imperativa;
disp oe de uma camada objecto;
permite uma integracao completa de programas C com programas
OCaml;
suporta a programac ao concorrente, distribuda e paralela;
disp oe de uma base contributiva importante, diversicada e completa
(como atestam o sub-site Hump do site ocial ou ainda o agregador
6
de projectos Caml ocamlforge, etc. ). Estes pontos de entrada permi-
tem, por exemplo, ter acesso a bibliotecas ricas que complementam a
biblioteca standard.
2 Compilacao, Execucao e Bibliotecas
Um programa OCaml apresenta-se como uma sequencia de express oes
OCaml eventualmente colocadas num cheiro .ml e/ou .mli (um cheiro
.mli agrupa a interface do cheiro .ml ao qual est a relacionado, tal como
um cheiro .h e o header relacionado com um cheiro .c na linguagem
C).
Podemos executar programas OCaml de tres formas diferentes. Para
cada uma delas ser ao precisos procedimentos diferentes.
2.1 Execucao
Interpretacao pelo toplevel : O ciclo de interaccao (toplevel na termi-
nologia inglesa) interpreta frase por frase (ou express ao OCaml por
express ao) o programa OCaml. Cada frase termina por dois pontos
e virgulas: ;;. Para cada express ao interpretada o toplevel devolve
o tipo e o valor da expressao em caso de sucesso. O toplevel e um
programa chamado ocaml. Os exemplos deste documento foram todos
escrito e executados utilizando o toplevel.
Para carregar uma biblioteca (OCaml ou pessoal) e preciso dis-
por do cheiro .cmo da biblioteca. O comando no toplevel e
#load ch . cmo;; . Pode-se tambem optar por uma inclus ao textual
(equivalente a um copy-past do cheiro para o toplevel). Neste caso o
comando e #use ch . ml ;;
Dica: preferir o comando ledit ocaml ou rlwrap ocaml ao comando
ocaml se os programas ledit ou rlwrap estiverem instalado na
m aquina hospede. Estes programas permitem guardar um historico
dos comandos/expressoes OCaml introduzidos, ` a semelhanca do
doskey do DOS.
7
Execucao pela maquina virtual: a semelhanca do Java, os programas
OCaml podem ser compilados para serem executados por uma ma-
quina virtual. Temos assim um programa portavel e seguro (a m a-
quina virtual tem mecanismos de seguranca embebidos) que pode ser
executado, sem qualquer modicac ao, numa grande variedade de arqui-
tecturas. Para obter tal programa, a compilac ao ter a de ser efectuada
utilizando o compilador para bytecode. A maquina virtual OCaml
chama-se ocamlrun. A execuc ao dum programa bytecode, digamos
prog, a partir dum terminal pode assim ser realizada alternativamente
de duas formas:
1. ocamlrun prog
2. ./prog
No caso do programa bytecode ter a maquina virtual embebida (gra cas
` a op cao -custom do compilador), basta utilizar a segunda alternativa
para a execuc ao do programa;
Execucao pela maquina hospeda: o utilizador pode querer privilegiar a
eciencia em rela cao a portabilidade do programa denido. Neste caso
preferir a utilizar o compilador nativo ocamlopt para produzir c odigo
m aquina bem mais rapido do que o codigo interpretado. Nesta con-
gurac ao:
o programa produzido e especco ` a m aquina em que foi compilado
mas ganha-se um factor de rapidez `a volta de 10;
o c odigo produzido e c odigo m aquina e e executado directamente
pela m aquina hospeda (como qualquer programa execut avel com-
pilado pelo compilador gcc por exemplo).
2.2 Compilacao
Existem dois compiladores (e duas variantes optimizadas de cada um deles,
suxadas por .opt).
ocamlc: este compilador gera codigo intermedio adequado para a m aquina
virtual OCaml;
ocamlopt: este compilador gera c odigo m aquina adequado para ser execu-
tado directamente pela maquina hospeda.
8
Ambos aceitam as op coes habituais dos compiladores da GNU (como o
gcc).
Destacamos as opc oes seguintes:
-i: gera para a sada standard toda a informac ao de tipos contida no
cheiro compilado.
Util para obter um cheiro .mli por defeito;
-c compila mas nao faz a edic ao das ligac oes (n ao faz o linking).
Aquando da utilizac ao do compilador ocamlc, permite gerar o .cmo
correspondente;
-custom permite a criacao de executaveis aut onomos. Aquando da uti-
lizac ao do compilador ocamlc, permite incorporar uma m aquina virtual
ao c odigo produzido. Com um executavel deste tipo n ao e assim neces-
s aria a presenca duma m aquina virtual na m aquina hospeda para que
o programa possa ser executado..
-o nome : indica o nome desejado para o execut avel;
-I caminho : acrescenta o caminho na lista das pastas onde procurar
bibliotecas.
-annot: produz informacao suplementar sobre tipos, tail-calls, bin-
dings, etc... para o cheiro <filename>.annot.
Util para quem utiliza
o editor emacs.
-a: compila para o formato biblioteca.
-pp <command>: activa o preprocessamento <command> via camlp4 ou
camlp5.
-unsafe: gera c odigo sem vericac ao dos acessos a strings e vectores.
2.3 Acesso `as bibliotecas
As func oes, valores das bibliotecas (tambem designados de modulos em
OCaml) sao acessveis de duas formas: directamente (se as bibliotecas fo-
ram previamente carregadas por um open ou use) ou por nome qualicados.
Por exemplo: seja f uma func ao da biblioteca B. O acesso directo a f e f, o
acesso qualicado e B.f. Repare que o acesso directo e condicionado ao facto
de n ao haver ambiguidades no nome f. Imagine que duas bibliotecas abertas
9
fornecem uma func ao f, ter a neste caso de qualicar o nome f para ter a
certeza de chamar a func ao realmente desejada. Para ser preciso, quando
dois modulos abertos disponibilizam o mesmo valor f, ent ao, pelas regras ha-
bituais de visibilidade, a invocac ao de f designa o valor f do ultimo modulo
aberto.
Os comandos para o carregamento de bibliotecas s ao, se tomarmos o
exemplo da biblioteca chamada bib:
#use "bib.cmo" no toplevel;
open Bib no cheiro .ml destinado para a compila cao
No caso dos nomes qualicados, o nome da biblioteca comeca sempre por
uma mai uscula.
2.4 Ambiente de Programacao
O ambiente classico para a programac ao OCaml e o editor emacs ou o editor
xemacs equipados com o modo tuareg.
Existe igualmente modos OCaml para os IDEs tradicionais. Citemos por
exemplo o ODT e o OCAIDE para o IDE eclipse.
De uma forma geral, os editores de texto populares como o editor crim-
son ou o notepad++ disponibilizam mecanismos satisfat orios de colorac ao de
sintaxe e de compila cao em linha de comando.
A instalac ao de uma distribuic ao OCaml de base em ambiente Win-
dows necessita de um compilador de assembly tradicionalmente fornecido
por plataformas como o MinGW ou CygWin. Um tutorial de instala cao
neste sistema operativo pode ser visualizado aqui.
O utilizador curioso podera tambem experimentar o site
http://try.ocamlpro.com/ que apresenta um ambiente de execuc ao OCaml
online completo.
A distribuic ao mnima de OCaml encontra-se no site http://caml.inria.fr.
Existe no entanto duas plataformas (complementares e relacionadas) que
permitem gerir a instalacao e a actualizac ao de uma quantidade apreci avel
de ferramentas, aplicac oes, bibliotecas, etc.. ligados `a linguagem OCaml.
Essas sao o GODI e o Oasis.
Para a compilac ao de projectos OCaml existam duas formas alternativas
muito praticas disponveis: primeiro, um Makele generico para OCaml e,
segundo, uma ferramenta de compilacao de projecto Ocamlbuild.
10
A biblioteca standard de OCaml e, para o uso geral, relativamente com-
pleta, mas de facto de dimens ao reduzida quando comparada com API de
outras linguagens populares. V arios complementos, que visam colmatar esta
situac ao, existem. Citemos por exemplo o OCaml batteries ou a Janes Street
core Suite.
De uma forma geral, existem varios reposit orios web que agrupam pro-
jectos, informac oes, documentac ao, recursos, apontadores, etc... ligados ao
mundo OCaml. Citemos o Caml Hump, ou ainda Forge OCaml .
3 N ucleo funcional
Comecemos por alguns aspectos fundamentais proprios `a linguagem OCaml,
que ser ao em grande parte justicados nesta seccao:
OCaml e uma linguagem funcional fortemente tipicada.
Um programa OCaml e uma sequencia de express oes ou de declarac oes
Uma express ao OCaml reduz-se a um valor
Qualquer express ao OCaml tem um e um s o tipo bem determinado
OCaml e uma linguagem funcional fortemente tipicada. Como tal,
trata-se duma linguagem onde as express oes (nas quais inclumos expressoes
funcionais) tem um papel central. As construcoes essenciais da linguagem
s ao assim mecanismos de denic ao e aplica cao de func oes.
O resultado do calculo duma expressao e um valor da linguagem e a
execu cao dum programa e o c alculo de todas as express oes constituindo o
programa.
E possvel pedir ao OCaml que uma lista de express oes sejam avali-
ados sequencialmente, tal como aconteceria em C. Tal sequencia segue
o seguinte formato: expr1;expr2; ...;expr_n.
E importante perceber
que esta n ao foge ` as regras da programacao funcional. Uma sequencia
expr1;expr2; ...;expr_n e uma e uma so express ao como tal tem um
valor.
O valor da sequencia e o valor da ultima expressao (neste caso expr_n).
O tipo das expressoes expr1;expr2;...;expr_(n-1) deve ser unit. Se tal
n ao for o caso, uma mensagem de aviso (Warning) e mostrada.
A funcao val ignore: a -> unit permite remediar este problema.
48
Ao permitir mecanismos tradicionalmente imperativos, mesmo de forma
controlada como e o caso aqui, e necessario disponibilizar (tambem de forma
controlada) a sequencia para poder permitir o encadeamento de efeitos cola-
terais. A sequencia e o mecanismo para este efeito.
Exemplos:
# print_int 1; 2 ; 3;;
Characters 13-14:
print_int 1; 2 ; 3;;
^
Warning S: this expression should have type unit.
1- : int = 3
# print_int 1; ignore 2; 3 ;;
1- : int = 3
# print_string ("1+1 = "^string_of_int (1+1));print_newline (); 4+1 ;;
1+1 = 2
- : int = 5
4.3.2 Blocos de programa
Para delimitar grupos de expressoes podemos utilizar alternativamente as
construc oes seguintes:
( expr )
ou
begin expr end
4.3.3 Ciclos
Sintaxe:
for nome = expr1 to expr2 do expr3 done
for nome = expr1 downto expr2 do expr3 done
while expr1 do expr2 done
Exemplos:
49
# let rec emais_emenos n =
print_string "Introduza um numero : ";
let i = read_int () in
if i = n then print_string "BRAVO\n\n"
else
begin
if i < n
then print_string "E+\n" else print_string "E-\n";
emais_emenos n
end ;;
val emais_emenos : int -> unit = <fun>
# for i=10 downto 1 do
print_int i; print_string " "
done;
print_newline() ;;
10 9 8 7 6 5 4 3 2 1
- : unit = ()
# let r = ref 1
in while !r < 11 do
print_int !r ;
print_string " " ;
r := !r+1
done ;;
1 2 3 4 5 6 7 8 9 10 - : unit = ()
4.3.4 Tipagem, aplicacao parcial e fechos
Voltemos agora ao conceito de tipagem fraca, mas desta vez na presenca de
referencias.
Imaginemos que queremos denir e utilizar uma referencia para uma lista.
A sua denic ao podera ser
# let rl = ref [];;
val rl : _a list ref = {contents = []}
# let reset () = rl := [];;
val reset : unit -> unit = <fun>
Nesta situac ao, e de facto impossvel prever o tipo dos elementos da lista
50
referenciada. Tal poder a acontecer quando se juntara o primeiro elemento a
lista referenciada por rl.
Imaginemos que estamos numa situac ao em que nao haja o mecanismo
dos tipos fracos. Ap os a primeira introduc ao de elemento (digamos um in-
teiro, como em rl := 1 ::rl!), ent ao a lista referenciada passa a ser de tipo
int list (ou seja rl teria agora por tipo int list ref).
Se utilizarmos a seguir a func ao reset, a referencia rl apontaria para uma
lista totalmente nova, vazia, logo com a vari avel de tipo por instanciar (de
tipo a list).
Nesta situac ao seria totalmente viavel juntar um caracter ` a lista
(rl := a ::rl!) e assim instanciar o tipo da lista referenciada para
char list.
Neste caso rl teria agora por tipo char list ref ou que introduziria
uma inconsistencia no sistema de tipo: a referencia rl n ao pode ser de dois
tipos fechados (fechados = completamente instanciados) diferentes.
O tipo de val rl : _a list ref = {contents = []} permite evitar
exactamente esta situac ao.
# let rl = ref [];;
val rl : _a list ref = {contents = []}
# let reset () = rl := [];;
val reset : unit -> unit = <fun>
# rl := 1 :: !rl;;
- : unit = ()
# rl ;;
- : int list ref = {contents = [1]}
# reset ();;
- : unit = ()
# rl ;;
- : int list ref = {contents = []}
# rl := a :: !rl;;
Error: This expression has type char but an expression was expected of type
int
Para terminar, vejamos porque os tracos imperativos devem ser mistura-
dos com processos funcionais com muita cautela.
# let f x = rl := x :: !rl; fun y -> y + 1;;
51
val f : int -> int -> int = <fun>
# f 5 2;;
- : int = 3
# rl ;;
- : int list ref = {contents = [5]}
# f 6 4;;
- : int = 5
# rl;;
- : int list ref = {contents = [6; 5]}
# let g x = f x;;
val g : int -> int -> int = <fun>
# g 8;;
- : int -> int = <fun>
# rl;;
- : int list ref = {contents = [8; 6; 5]}
# let h = f 9;;
val h : int -> int = <fun>
# rl;;
- : int list ref = {contents = [9; 8; 6; 5]}
# h 7;;
- : int = 8
# rl;;
- : int list ref = {contents = [9; 8; 6; 5]}
# h 10;;
- : int = 11
# rl;;
- : int list ref = {contents = [9; 8; 6; 5]}
Como podemos ver no exemplo anterior, cada aplica cao completa de f
permite, para alem de devolver o valor inteiro desejado, a execuc ao do efeito
colateral declarada no corpo da funcao. A aplicac ao parcial de f resultante
na func ao h faz com que a execuc ao do efeito colateral seja efectuada uma
s o vez (no momento da denic ao de h), cando assim separada do calculo do
valor inteiro consecutivo. A avaliacao parcial quando combinada com efeitos
colaterais pode dar resultados surpreendentes ` a primeira vista.
Outra manifestac ao dos efeitos subtis da avalia cao quando combinada
com efeitos colaterais e o efeito da ordem de avaliac ao das express oes.
#print_int 1; print_int 2; print_int 3; print_int 4 ;;
52
1234- : unit = ()
# let a = (print_int 1; 1) * (print_int 2; 2) in
let b = (print_int 3; 3) * (print_int 4; 4) in
a + b;;
2143- : int = 14
# let b = (print_int 3; 3) * (print_int 4; 4) in
let a = (print_int 1; 1) * (print_int 2; 2) in
a + b;;
4321- : int = 14
# (print_int 1; 1) * (print_int 2; 2) +
(print_int 3; 3) * (print_int 4; 4);;
4321- : int = 14
Conclus ao, a ordem de avaliac ao de expressoes nao e especicada nos do-
cumento de referencia de OCaml e resulta numa escolha de desenho do com-
pilador utilizado. O programador dever a evitar assim que os seus programas
dependem desta ordem de avaliacao, ou ent ao conhecer bem o compilador
usado.
5 Avaliacao Ansiosa, Avaliacao preguicosa e
estruturas de dados innitas
Uma caracterstica fundamental das linguagens de programac ao e a estrategia
de avaliacao.
E tao fundamental que molda por completo o desenho da
linguagem em causa e ate mesmo a forma de programar na dita.
N ao vamos nesta introducao explicitar toda a envolvente, deveras muito
instrutiva. Aconselhamos o leitor curioso em consultar os manuais classicos
de sem antica das linguagens de programac ao, ou os tratados cl assicos sobre
o calculo lambda.
O conceito de estrategia da avaliacao resume-se ` a escolha de desenho feita
pelo designer da linguagem sobre a forma de calcular (avaliar) uma chamada
a uma funcao. Comecamos por avaliar os parametros ou aguardamos que
estes sejam necessarios na aplicac ao da func ao?.
Por exemplo, para obter o valor de Ackermann (x + 2) (fact 4), op-
tamos por dar prioridade ` a func ao ackermann ou aos seus par ametros?
A primeira escolha nos daria uma redu cao (i.e. um passo de calculo) para:
53
if (x + 2) = 0
then (fact 4) + 1
else if (x + 2) >= 0 && (fact 4) = 0
then ackermann ((x + 2) - 1) 1
else ackermann ((x + 2) - 1) (ackermann (x + 2) ((fact 4) - 1))
enquanto a segunda nos levaria ` a avaliac ao de ackermann x+2 24.
A primeira escolha e designada por avaliac ao preguicosa (lazy evaluation;
no sentido seguinte: avaliamos uma express ao so quando o seu uso se revela
necess ario)
A segunda escolha e designada de avalia cao ansiosa (eager evaluation).
No que diz respeito a avaliacao das chamadas ` a func oes, o desenho da lin-
guagem OCaml repousa, ` a semelhanca de uma vasta gama de linguagens de
programac ao, sobre esta ultima avaliac ao. A avaliac ao ansiosa e mecanismo
de avaliacao por defeito (i.e. implcito). Veremos mas adiante que podemos
no entanto optar explicitamente pela primeira.
Assim antes de explicar mais em detalhe o mecanismo de avaliac ao em
OCaml, citemos um exemplo famoso que abona na avalia cao preguicosa (que
no entanto tem tambem os seus desaos por resolver).
# let ciclo n = while true do () done; n;;
val ciclo : a -> a = <fun>
# let tricky n m = n + 1;;
val tricky : int -> a -> int = <fun>
#(* N~ ao tente isso em casa, esta avalia c~ ao
e feita por especialistas treinados*)
tricky (5 + 2) (ciclo 7);;
Numa linguagem, como Haskell, onde a avalia-
c ao e por defeito preguicosa o resultado teria sido
tricky (5 + 2) (ciclo 7) -> (5 + 2) + 1 -> 8.
5.1 Avaliacao em OCaml
Comecemos por relembrar as regras que oportunamente ao longo deste docu-
mento foram dadas sobre a ordem de avaliac ao em OCaml. Completaremos
este lembrete com alguns aspectos tecnicos relevantes para esta secc ao.
A avaliac ao de uma sequencia de instruc oes (que, lembremos, e separada
por ;) e avaliada da esquerda para a direita.
54
# print_string "1 "; print_string "2 ";print_string "3 "; print_string "4 ";;
1 2 3 4 - : unit = ()
Recordemos os efeitos, de aparencia esoterica, da mistura de efeitos late-
rais (com base por exemplo nestas sequencias) com avaliac ao parcial.
A expressao let x = e1 in e2 e avaliada na seguinte ordem: primeiro
e1, depois a atribui cao do valor e1 ` a variavel x e nalmente e avaliada a
express ao e2. Este caso e mais uma ilustrac ao da avaliacao ansiosa.
# let x = 8;;
val x : int = 8
# let x = print_int x; x + 1 in
let x = print_string "ola"; x + 2 in
print_string ("-->"^string_of_int x^"\n"); x+3;;
8ola-->11
- : int = 14
A express ao if b then e1 else e2 avalia-se da seguinte forma: pri-
meiro e avaliado a expressao b e depois, consoante o valor da avaliac ao de b,
e avaliada a express ao e1 ou (de forma exclusiva) entao e2.
# if true then print_string "ola" else (raise Not_found);;
ola- : unit = ()
Podemos dizer aqui que na express ao if b then e1 else e2 a expres-
s ao b e avaliada de forma ansiosa enquanto e1 e e2 s ao avaliados de forma
preguicosa.
Encontramos este tipo de desenho em outras situacoes em OCaml.
# (true) || (print_string "ola\n"; false);;
- : bool = true
# (false) || (print_string "ola\n"; false);;
ola
- : bool = false
# (false) && (print_string "ola\n"; false);;
- : bool = false
55
A avaliacao de uma express ao, como em f x y z, segue a regra seguinte:
primeiro e avaliada a lista de expressoes (x,y, e z) depois e avaliado a funcao
f com os valores resultantes da avaliac ao de x,y, e z.
Um ponto subtil e importante e referente ` a ordem de avaliac ao da sequen-
cia de expressoes x,y, e z. Esta ordem n ao e especicada. Uma das raz oes
e deixar espaco aos designers de compiladores OCaml para optimiza coes
futuras que podem ser sensveis ` a ordem de avaliac ao destas situac oes.
# let f a b = a + b;;
val f : int -> int -> int = <fun>
# let v = f ((1 * 2) + (3 + 4)) ((5 * 6) + (7 + 8));;
val v : int = 54
# let v1 = f (print_string "A"; ( ( print_string "B";1) *
( print_string "C";2)) + (( print_string "D";3) +
( print_string "E";4)))
(print_string "F"; ( ( print_string "G";5) *
( print_string "H";6)) + (( print_string "I";7) +
( print_string "J";8)));;
FJIHGAEDCBval v1 : int = 54
#
Como vemos neste exemplo a vers ao actual da distribui-
c ao de OCaml utilizada avalia listas de express oes (como em
((1 * 2) + (3 + 4)) ((5 * 6) + (7 + 8))) da direita para esquerda.
Esta escolha, num contexto puramente funcional, n ao tem importancia
nenhuma. De facto, em (5 + 6) (7 + 8), avaliar (5 + 6) antes de (7 + 8),
ou vice versa, e irrelevante. Infelizmente num contexto em que se mistura
efeitos co-laterais com computa cao, tal deixa de ser sem consequencias. O
programador dever a ter consciencia desse fenomeno.
5.2 Avaliacao Preguicosa em OCaml
E esta situac ao
que provoca a excepcao (e o aviso de que o padrao seguinte nao serve para
nada).
A soluc ao e Nunca ignorar uma mensagem de aviso e aninhar ltragens.
# let rec comp_par s = match s with parser
[<x >] ->
(match s with parser
[<y >] -> comp_par s
| [<>] -> false)
| [<>] -> true;;
val comp_par : a Stream.t -> bool = <fun>
# comp_par [<1; 1; 1; 1;1>];;
- : bool = false
68
Para terminar mostramos uma caracterstica muito interessante da ltra-
gem de motivo: o consumo Cavaco Silva. Ou seja o consumo interno, porque
o que e nosso e bom.
# let rec comprimento s = match s with parser
[<x; c = comprimento >] -> 1 + c
| [<>] -> 0;;
val comprimento : a Stream.t -> int = <fun>
# comprimento [<1; 1; 1; 1;1>];;
- : int = 5
Esta func ao poderia ter sido escrita classicamente como:
# let rec comprimento s = match s with parser
[<x >] -> 1 + (comprimento s)
| [<>] -> 0;;
val comprimento : a Stream.t -> int = <fun>
# comprimento [<1; 1; 1; 1;1>];;
- : int = 5
Veremos mais adiante, na sec cao dos exemplos completos, que esta pe-
quena caracterstica torna o uso dos uxos muito interessante.
6 Modulos
Esta secc ao e fortemente baseada nos apontamentos cedidos gen-
tilmente por Jean-Christophe Filli atre (Initiation ` a la programma-
tion fonctionnelle. Master Informatique M1, Universite Paris-Sud.
http://www.lri.fr/~filliatr/m1/cours-ocaml.en.html).
Quando os programas tornam-se grandes,e importante que a linguagem
de programac ao de suporte forneca boas ferramentas de Engenharia de Soft-
ware. Tais ferramentas dever ao, em particular, permitir uma estruturac ao
do codigo em unidades de tamanho razoavel (modularidade), ocultar a re-
presentac ao concreta de certos dados (encapsulamento) e evitar da melhor
forma a duplicac ao de c odigo.
Existe diversas formas para atingir tais objectivos. Na programac ao ori-
entada ` a objectos, as classes e os conceitos associados asseguram esse suporte.
Em OCaml, para alem da camada objecto existente, estas funcionalidades
s ao alternativamente suportadas pelo sistemas de modulos.
69
Como o nome de modulo o sugere, um m oduloe essencialmente uma forma
de introduzir modularidade num programa, i.e. de o estruturar em unidades
de tamanho razo avel.
6.1 Ficheiros e modulos
A unidade programatica mais simples no contexto dos m odulose o cheiro.
Em OCaml, cada cheiro constitu um m odulo diferente. Se este, digamos
arith.ml, conter um determinado n umero de declarac oes,
let pi = 3.141592
let round x = floor (x +. 0.5)
ent ao a compilac ao deste cheiro, visto como um m odulo, faz-se invocando
uma compilac ao sem edic ao de liga coes gra cas `a op cao -c do compilador
(como no caso do gcc).
% ocamlc -c arith.ml
O resultado desta compila cao e composta por dois cheiros: arith.cmo
contendo o c odigo e arith.cmi contendo a sua interface (.i.e. uma constante
pi de tipo float e uma func ao round de tipo float -> float).
O nome do moduloe o nome do cheiro, iniciado por uma mai uscula. No
caso concreto do exemplo,e Arith.
Podemos assim referenciar os elementos deste m odulo noutro cheiro
OCaml pelo uso da notac ao Arith.x. Assim podemos utilisar o m odulo
Arith num cheiro main.ml :
let x = float_of_string (read_line ());;
print_float (Arith.round (x /. Arith.pi));;
print_newline ();;
e compilar este ultimo da seguinte forma:
% ocamlc -c main.ml
70
Para que esta compila cao seja bem sucedidae necessario que o m odulo
Arith esteja previamente compilado. De facto, quandoe encontrada uma
referencia a um elemento do modulo, como aqui Arith.pi, o compilador
procura um cheiro de interface arith.cmi. Podemos assim realizar a edicao
das ligac oes, fornecendo os dois cheiros de c odigo ao compiladorOCaml:
% ocamlc arith.cmo main.cmo
Uma forma alternativa possvel teria sido compilar directamente
omain.ml e assim realizar a compilac ao do cheiro de uma s o vez.
% ocamlc arith.cmo main.ml
Ou ainda realizar a compilac ao de ambos os cheiros fontes e a edi cao
das ligac oes de uma s o vez.
% ocamlc arith.ml main.ml
Neste ultimo caso os cheiros sao compilados na ordem apresentada, logo
a dependencia do ultimo cheiro sobre o primeiroe respeitada e produz assim
o efeito desejado.
6.2 Encapsulamento
Os m odulos, na sua vers ao cheiro, permitam a estrutura cao de um programa
(em unidades de codigo). Mas os modulos permitam muito mais do que isso,
por exemplo o encapsulamento, i.e. a ocultac ao de determinados detalhes
de c odigo. De facto,e possvel fornecer uma interface aos m odulos denidos.
Neste caso so os elementos declarados na interface ser ao visveis, `a semelhan ca
do mecanismo Java representado pela palavra chave private que permite
controlar (limitar, aqui) a visibilidade dos atributos ou dos metodos.
Para tal, coloca-se a interface num cheiro com a extensao .mli na mesma
pasta onde se encontra o cheiro fonte (de extens ao .ml).
Assim podemos querer exportar somente a func ao round do m odulo Arith
previamente denido, criando o cheiro arith.mli com o conte udo:
val round : float -> float
71
Constata-se que a sintaxee a mesma que a sintaxe utilizada pelo toplevel
de OCaml na sua interacc ao (na resposta para ser mais preciso) com o
programador.
Este cheiro interface deve ser convenientemente compilado antes do -
cheiro c odigo.
% ocamlc -c arith.mli
% ocamlc -c arith.ml
Aquando da compilac ao do codigo, o compilador vericar a a adequac ao
dos tipos entre os elementos declarados na interface e os elementos de facto
fornecidos no codigo.
No caso do exemplo anterior, compilar o modulo main.ml resultar a no
erro:
% ocamlc -c main.ml
File "main.ml", line 2, characters 33-41:
Unbound value Arith.pi
No entanto a constante pi permanece obviamente visvel e acessvel aos
elementos presentes no cheiro arith.ml.
A interface n ao se limita na restric ao dos valores exportados. Permite
tambem restringir os tipos exportados ou ate mesmo na denicao destes.
Suponhamos que queiramos programar uma pequena biblioteca sobre con-
juntos de inteiros representados por listas. O c odigo poderia ser constitudo
pelo cheiro ensemble.ml seguinte:
type t = int list
let vazia = []
let junta x l = x :: l
let pertence = List.mem
e uma interface possvel seria:
type t = int list
val vazia : t
val junta : int -> t -> t
val pertence : int -> t -> bool
72
Aqui a denic ao do tipo t n aoe de nenhuma utilidade, excepto, eventu-
almente, facilitar a leitura da interface pela identicac ao dos conjuntos ao
tipo t. Sem a mencao do tipo, o compilador teria inferido ele pr oprio esta
informacao e poderia ter entao atribudo um tipo mais geral mas compatvel
com as func oes vazia, junta e pertence.
Mas podemos tambem querer esconder a representac ao escolhida do tipo
t, dando na interface o conte udo seguinte:
type t
val vazia : t
val junta : int -> t -> t
val pertence : int -> t -> bool
Um tal tipo designa-se de tipo abstracto. Dentro do cheiro ensemble.ml
a representac ao do tipo te conhecida, forae desconhecida.
E uma noc ao
essencial sobre a qual nos debrucaremos mais adiante aquando da exploracao
da nocao de persistencia.
O facto de abstrair o tipo t esconde por completo a forma como os va-
lores deste tipo s ao formados. Se procuramos processar um valor do tipo
Ensemble.t como uma lista de inteiros fora do dito cheiro, obteremos um
erro.
Assim, podemos modicar a codicac ao da nocao de conjunto sem nunca
perturbar os programas que usam as funcionalidades fornecidas pelo modulo
Ensemble.
6.2.1 Compilacao separada
A linguagem OCaml permite de facto a compilacao separada. Isto e, a
compila cao dum cheiro so depende das interfaces dos modulos que menciona
e n ao do codigo que lhe estao associadas.
Assim se um m odulo B utiliza um m odulo A, uma altera cao do c odigo
do m odulo A n ao obriga a recompilac ao do m odulo B. Somente uma altera-
c ao da interface de A justicaria tal recompilac ao. Isto porque seria, neste
caso, necessario vericar se altera coes aos tipos dos valores exportados por A
preservam a boa tipicacao de B.
A compilac ao separada representa um ganho de tempo consider avel para
o programador. Neste contexto e para um projecto constitudo por cem -
73
cheiros OCaml, uma altera cao do codigo s o implicar a uma unica compila cao
e uma edic ao de ligac oes para reconstruir o execut avel.
6.2.2 Linguagem de modulos
Uma das for cas do sistema de m odulos de OCaml e ele nao se limitar aos
cheiros. Podemos de facto denir um novo m odulo da mesma forma que
podemos denir um tipo, uma constante, uma func ao ou uma excepc ao.
Assim pode mos escrever:
module M = struct
let c = 100
let f x = c * x
end
A palavra chave module introduz aqui a declaracao de um novo m odulo,
M, cuja denicaoe o bloco iniciado por struct e concludo por end.
Tal blocoe designado de estrutura (o que justica a palavra chave struct)
ee composto de uma sequencia de declarac oes e/ou de expressoes OCaml,
exactamente como no caso dum programa OCaml. Tais declara coes de
m odulos podem ser misturadas com outras declarac oes e ate mesmo serem
aninhadas e assim conter outras declaracoes de modulos. Poderamos assim
escrever algo como:
module A = struct
let a = 2
module B = struct
let b = 3
let f x = a * b * x
end
let f x = B.f (x + 1)
end
Tais m odulos locais ganham particular import ancia com a utiliza cao
dos functores que serao abordados mais adiante na seccao seguinte. Mas
podemos desde j a constatar que estes permitam uma organizac ao do espaco
de nomes. Assim no exemplo anterior, pudemos denir duas func oes de nome
f, porque a func ao denida no m odulo B n aoe imediatamente visvel no corpo
do m odulo A.
E necess ario qualicar, com a notacao B.f, para de facto ter
acesso a tal funcao dentro do modulo A.
74
Podemos assim servirmo-nos dos m odulos locais para agrupar determina-
dos tipos e func oes interligados e dar-lhes nomes genericos (como add, find,
etc.) porque a qualicac ao permite-lhes serem distinguidos de outras fun coes
com o mesmo nome.
Quando tais valores s ao referenciados frequentemente, podemos evitar
a qualicac ao sistematica pelo nome do m odulo abrindo explicitamente o
m odulo com a ajuda do comando open. A consequencia desta aberturae
tornar visveltodos os elementos deste m odulo.
Assim, no lugar da invocacao sistem atica Printf.printf, preferiremos
abrir o modulo Printf no inicio do cheiro :
open Printf
...
let print x y = printf "%d + %d = %d\n" x y (x+y)
...
Assinaturas
`
A semelhanca de qualquer outra declarac ao, as declarac oes de m odulos sao
tipicadas (i.e. os m odulos tem tipos).
O tipo de um m odulo designa-se por assinatura. A sintaxe de uma assina-
turae identica `a de uma interface (os cheiros .mli) ee iniciada pela palavra
chave sig e terminada pela palavra chave end. Constatamo-lo na seguinte
declara cao no toplevel:
# module M = struct let a = 2 let f x = a * x end;;
module M : sig val a : int val f : int -> int end
E l ogico que a sintaxe seja identica a de uma interface porque uma inter-
face n aoe nada mais de que o tipo de um m odulo-cheiro.
Da mesma forma que se pode denir modulos locais, podemos denir
assinaturas:
# module type S = sig val f : int -> int end;;
module type S = sig val f : int -> int end
Podemos ent ao servirnos de tais assinaturas para tipicar explicitamente
as declaracoes de m odulos:
# module M : S = struct let a = 2 let f x = a * x end;;
module M : S
75
O interesse de tais declara coese o mesmo que o interesse das interfaces:
rstringir os valores ou tipos exportados (que designamos anteriormente de
encapsulamento). Aqui o valor M.a deixou de ser visvel:
# M.a;;
Unbound value M.a
Resumo
O sistema de modulos OCaml permite
modularidade, pela estruturacao do c odigo em unidades designadas de
modulos;
encapsulamento de tipos e valores, e em particular da denic ao de tipos
abstractos;
uma verdadeira compilacao separada;
uma organizac ao do espaco de nomes.
6.3 Functores
Da mesma forma que uma func ao OCaml pode ser generica relativamente
aos tipos dos seus par ametros (polimorsmo) ou `as func oes em par ametro
(ordem superior), um modulo pode ser parametrizado por um ou mais outros
m odulo. Um tal m odulo designa-se por functor.
Para devidamente justicar a utilidade dos functores, o mais simples e
ilustra-los por um exemplo.
Suponhamos que queiramos codicar em OCaml uma tabela de hash da
forma mais generica possvel por forma a que esse c odigo possa ser utilizado
em qualquer circunstancia em que uma tabela de hash possa ser util.
Para issoe preciso parametrizar o codigo por uma fun cao de de hash (para
a insercao na tabela e a pesquisa) e por uma func ao de igualdade (para a
pesquisa)
5
.
Poderamos ent ao imaginar utilizar a ordem superior e parametrizar todas
as func oes do nosso m odulo por estas duas funcoes, o que resultaria numa
interface da forma:
5
Acontece que OCaml predene uma fun cao de igualdade e uma funcao de hash poli-
morca, mas em algumas situa coes o programador pode desejar utilizar funcoes diferentes.
76
type a t
(* o tipo abstracto das tabelas de hash contendo elementos de
tipo "a" *)
val create : int -> a t
(* "create n" cria uma nova tabela de hash de tamanho inicial "n" *)
val add : (a -> int) -> a t -> a -> unit
(* "add h t x" junta o valor "x" na tabela "t" com a ajuda da
func~ ao de hash "h" *)
val mem : (a -> int) -> (a -> a -> bool) -> a t -> a -> bool
(* "mem h eq t x" testa a ocorr^ encia de "x" na tabela "t" para a
func~ ao de hash "h" e a igualdade "eq" *)
Para alem do aspecto pesado, esta interface tem os seus perigos: de facto
podemos juntar um elemento na tabela com uma certa fun cao de hash e de-
pois pesquisar nesta mesma tabela com outra funcao de hash. O resultado
seria errado, mas o sistema de tipos de OCaml n ao teria excludo esta utili-
zac ao incorrecta (de facto, apesar do argumento apresentado, esta utilizac aoe
bem tipicada).
Uma solu cao mais satisfat oria consistiria em atribuir uma fun cao de hash
no momento da criac ao da tabela de hash. Estas seriam entao agrupadas
na estrutura de dados e utilizadas pelas operacoes de cada vez que seria
necess ario.
A interface caria ent ao mais razo avel:
type a t
(* o tipo abstracto das tabelas de hash contendo elementos de
tipo "a" *)
val create : (a -> int) -> (a -> a -> bool) -> int -> a t
(* "create h eq n" cria uma nova tabela de hash de tamanho
inicial "n", com "h" como func~ao de hash e "eq" como
func~ ao de igualdade *)
val add : a t -> a -> unit
(* "add t x" junta o dado "x" na tabela "t" *)
val mem : a t -> a -> bool
(* "mem t x" testa a ocorr^ encia de "x" na tabela "t" *)
A representa cao interna da codicac ao poderia se assemelhar a algo como:
77
type a t = { hash : a -> int; eq : a -> a -> bool; data : a list array }
let create h eq n = { hash = h; eq = eq; data = Array.create n [] }
...
No entanto, tal codicac ao ainda tem os seus defeitos. Por comecar,
o acesso `as func oes de hash e de igualdade fazem-se necessariamente pelo
intermedio de fechos e como tal penaliza a execuc ao relativamente a fun coes
conhecidas estaticamente. Por outro lado a estrutura de dados codicando
a tabela contem agora func oes e por esta raz ao torna-se complicado escrever
tal representacao em dispositivos fsicos, como o disco, e reler posteriormente
(e tecnicamente possvel, mas com recurso a importantes restri coes).
Felizmente, os functores trazem aqui uma soluc ao muito satisfatoria. Por-
que pretendemos parametrizar as nossas tabelas de hash por um tipo (o dos
seus elementos) e duas fun coes, vamos escrever um m odulo parametrizado
por um outro m odulo contendo estes tres requisitos. Tal functor F, parame-
trizado por um m odulo X com assinatura S, introduz-se com a sintaxe
module F(X : S) = struct ... end
No caso do nosso exemplo a assinatura Se a seguinte:
module type S = sig
type elt
val hash : elt -> int
val eq : elt -> elt -> bool
end
e a codicacao das tabelas de hash pode entao se escrever da seguinte
forma:
module F(X : S) = struct
type t = X.elt list array
let create n = Array.create n []
let add t x =
let i = (X.hash x) mod (Array.length t) in t.(i) <- x :: t.(i)
let mem t x =
let i = (X.hash x) mod (Array.length t) in List.exists (X.eq x) t.(i)
end
78
Constatamos que no corpo do functor, acedemos aos elementos do
m odulo-parametro X exactamente como se de um m odulo (como denido
mais acima) se tratasse. O tipo do functor F explicita o par ametro X com a
mesma sintaxe:
module F(X : S) : sig
type t
(* O tipo abstracto das tabelas de hash contendo elemento de tipo
"X.elt" *)
val create : int -> t
(* "create h eq n" cria uma nova tabela de tamanho inicial "n" *)
val add : t -> X.elt -> unit
(* "add t x" junta o valor "x" na tabela "t" *)
val mem : t -> X.elt -> bool
(* "mem t x" testa a ocorr^ encia de "x" na tabela "t" *)
end
Podemos ent ao instanciar o functor F com um qualquer modulo escolhido
pelo programador, desde que respeite as assinaturas pretendidas.
Assim se desejamos uma tabela de hash para arquivar inteiros, podemos
comecar por denir um m odulo Integers com assinatura S :
module integers = struct
type elt = int
let hash x = abs x
let eq x y = x=y
end
Podemos, a seguir, aplicar o functor F sobre este module :
module Hintegers = F(Integers)
Constatamos assim que a denic ao de um m odulo n ao est a limitado a
uma estrutura: trata-se de uma aplicac ao de um functor.
Podemos entao utilizar o m odulo Hintegers como qualquer outro m odulo
:
79
# let t = Hintegers.create 17;;
val t : Hintegers.t = <abstr>
# Hintegers.add t 13;;
- : unit = ()
# Hintegers.add t 173;;
- : unit = ()
# Hintegers.mem t 13;;
- : bool = true
# Hintegers.mem t 14;;
- : bool = false
...
6.3.1 Aplicacoes
As aplicacoes dos functores s ao m ultiplas. Utilizam-se em particular para
denir:
1. estruturas de dados parametrizadas por outras estruturas de dados
OCaml oferece tres deste functores na sua biblioteca standard:
Hashtbl.Make : tabelas de hash semelhantes ` as do exemplo ante-
rior;
Set.Make : conjuntos nitos codicados com base em arvores ba-
lenceadas;
Map.Make : tabelas de associa cao codicadas com base em arvores
balenceadas.
2. algoritmos parametrizados por estruturas de dados
Podemos por exemplo escrever o algoritmo de Dijkstra de procura de
caminho mais curto num grafo sob a forma de um functor parametri-
zado pela estrutura de dados representando o grafo em si. O tipo de
tal functor poderia ser:
module Dijkstra
(G : sig
type graph (* tipo dos grafos *)
type vertice (* tipo dos vertices *)
80
val vizinho : graph -> vertice -> (vertice * int) list
(* conjunto dos vizinhos com peso nas arestas *)
end) :
sig
val shortest_path :
G.graph -> G.vertice -> G.vertice -> G.vertice list * int
(* "shortest_path g a b" procura o caminho mais curto de
"a" para "b" em "g" *)
end
Vemos assim que os functores sao uma ferramenta poderosa para a reu-
tilizac ao de c odigo, porque permite a sua escrita de forma muito generica.
Os functores podem ser comparados, em certos aspectos, aos templates de
C++,mesmo se ha tambem diferencas numerosas e not orias.
De notar que o sistema de modulos de OCaml e, na realidade, indepen-
dente da linguagem de base. Forma uma linguagem por si s o, que poderia
ser aplicado a outra linguagem. Em particular ele poderia ser desdobrado
estaticamente para dar um c odigo sem m odulos nem functores, mesmo se na
pratica naoe isso que acontece.
7 Persistencia
Esta secc ao e fortemente baseada nos apontamentos cedidos gen-
tilmente por Jean-Christophe Filli atre (Initiation ` a la programma-
tion fonctionnelle. Master Informatique M1, Universite Paris-Sud.
http://www.lri.fr/~filliatr/m1/cours-ocaml.en.html).
Nesta sec cao abordaremos um aspecto essencial das estruturas de dados,
a persistencia. Esta nocao nao e especca `a programacao OCaml, nem, ate,
da programa cao funcional, mas e de facto natural nesta famlia de linguagens.
7.1 Estruturas de dados imutaveis
Como ja foi referido, muitas das estruturas de dados OCaml s ao imuta-
veis. Isto e, nao s ao alteraveis uma vez construdas. Este facto tem uma
consequencia muito importante: um valor de tal tipo nao e afectado pelas
81
operacoes das quais e par ametro. Novos valores s ao construdos e devolvidos
por estas ultimas.
Ilustremos este facto por exemplos simples.
Se denirmos uma lista l por let l = [1; 2; 3] ent ao l e, em termos
de representac ao memoria, um apontador para o primeiro bloco contento 1 e
um apontador para o segundo bloco, etc. :
1 2 3 []
l
Como referido previamente, se denirmos agora a lista l como a junc ao de
um outro elemento `a lista l, com a ajuda da declarac ao let l = 0 :: l,
temos agora a situacao seguinte:
0 1 2 3 []
l l
Isto e, a aplicac ao do construtor :: teve por efeitos alocar um novo bloco,
cujo primeiro elemento e 0 e o segundo um apontador cujo valor e igual `a l.
A vari avel l continua a apontar para os mesmos blocos.
De uma forma geral, qualquer fun cao denida que manipule listas tera
esta propriedade de n ao alterar as listas que lhe sao passadas como parame-
tros.
E por esta raz ao que as listas devem ser usadas quando o contexto do
seu uso considera juntar e retirar elementos na cabe ca (` a semelhanca das
estruturas de tipo pilha). Quando os acessos e/ou as modica coes devem ser
realizadas em posicoes arbitrarias na sequencia considerada, entao e prefervel
outra estrutura de dados.
e importante notar que os elementos proprios da lista n ao sao copiados
pela func ao append. De facto, x designa um elemento de tipo qualquer e
nenhuma c opia e realizada sobre o proprio x. No caso duma lista de inteiros,
isto pouca relevancia tem. Mas se consider assemos uma lista l contendo
83
tres elementos de um tipo mais complexo, por exemplo triplos de inteiros
como no caso de let l =[(1,2,3); (4,5,6); (7,8,9)], ent ao estes car ao
partilhados por l e append l [(10,11,12)] .
[]
l
[]
l
1 2 3 4 5 6 7 8 9 10 11 12
Isto por parecer articialmente custoso quando se tem o habito de traba-
lhar com estruturas de dados modicaveis localmente, come tradicionalmente
o caso no contexto de linguagens imperativas. Mas isto seria sub-estimar o
interesse pr atico da persistencia.
tal que
Caso de Base
110
1 X
, X
Construtores
F X, F X
F, G X, (F G) X
F, G X, (F G) X
F, G X, (F G) X
F, G X, (F G) X
Dois benefcios imediatos da deni cao por induc ao estrutural das formulas
proposicionais s ao a existencia de um principio de prova por induc ao (estru-
tural) e um princpio de recurs ao (estrutural) util para a programac ao sobre
estas estruturas.
O princpio de inducao para as f ormulas proposicionais pode ser descrito
resumidamente da seguinte forma.
Seja P uma propriedade sobre os elementos de Trop. Para provar
x Trop, P(x)
basta provar:
Casos de Base.
v 1, P(v)
P()
P()
Construtores
F Trop, se P(F) ent ao P(F)
F, G Trop, se P(F) e P(G) ent ao P((F G))
F, G Trop, se P(F) e P(G) ent ao P((F G))
F, G Trop, se P(F) e P(G) ent ao P((F G))
F, G Trop, se P(F) e P(G) ent ao P((F G))
111
O mecanismo de denic ao de func oes (estruturalmente) recursivas, desig-
nado de princpio de recorrencia para as f ormulas proposicionais, expoe-se
da seguinte forma.
Para denir uma fun cao f : Prop