O que é um efeito lateral? Um efeito lateral é qualquer atividade que uma função desenvolva para além de calcular um resultado a partir dos argumentos. Por exemplo:
Para sequenciar os efeitos laterais, o OCaml dispõe dum operador de sequenciação que se escreve ";" (como em Pascal!). Assim, por exemplo, para escrever no ecrã a string "ola", e logo a seguir a string "ole" usamos a seguinte função:
let hello () = print_string "ola" ; print_string "ole" ;;Tecnicamente, ";" é o nome duma função com o seguinte tipo:
";": unit -> 'b -> 'be a seguinte definição usada internamente pelo OCaml:
";" x y = y ;;
O tipo unit é um tipo básico com apenas um valor, que se escreve (). Para comparação, note que o tipo bool é um tipo com apenas dois valores, que se escrevem true e false. Por seu lado, o tipo void do C é um tipo sem valores e por isso não resolveria o nosso problema em OCaml.
Exemplos de utilização de unit e ().
print_string: string -> unit read_int: unit -> int print_newline: unit -> unit let x = read_int () in print_int (x+1)Exemplo de função para escrever uma lista de inteiros no ecrã:
let rec printList l = match l with | [] -> () | x::xs -> print_int x ; print_newline () ; printList xs ;;
As operações sobre ficheiros são efetuadas através de canais. Os canais são valores dos tipos predefinidos in_channel e out_channel.
Os "canais" do OCaml correspondem às "streams" do Java.
Em OCaml existem três canais predefinidos que são automaticamente abertos quando o programa começa a correr:
print_char: char -> unit print_string: string -> unit print_int: int -> unit print_float: float -> unit print_newline: unit -> unit
read_line: unit -> string read_int: unit -> int read_float: unit -> float
open_in: string -> in_channel open_out: string -> out_channel close_in: in_channel -> unit close_out: out_channel -> unit
output_char: out_channel -> char -> unit output_string: out_channel -> string -> unit flush: out_channel -> unit
input_char: in_channel -> char input_line: in_channel -> stringEsta lista de primitivas não é exaustiva. Há mais primitivas listadas no manual de referência (ver Basic Operations).
(* copyChannel: copia o canal de input ci para o canal de output co *) let rec copyChannel ci co = try let s = input_line ci in output_string co s ; output_string co "\n" ; copyChannel ci co with End_of_file -> () ;; (* copyFile: abre os ficheiros ni e depois usa a função copyChannel *) let copyFile ni no = let ci = open_in ni in let co = open_out no in copyChannel ci co ; close_in ci ; close_out co ;;Esquema geral de utilização do método indutivo no tratamento de ficheiros linha a linha:
let rec f ci = try let s = input_line ci in ... f ci ... with End_of_file -> ... ;;
Contudo, para aceder a módulos criados pelo utilizador, é preciso usar a diretiva #load assim:
# #load "MySet.cmo" ;;Para referenciar elementos dum módulo (seja um módulo de biblioteca ou um módulo do utilizador) existem duas alternativas.
Na primeira alternativa, podemos usar referências qualificadas, como se exemplifica:
# Sys.os_type ;; - : string = "Unix" # Sys.command "ls -l" ;; total 24 -rw-r--r-- 1 amd amd 259 2008-03-11 10:26 main.ml -rw-r--r-- 1 amd amd 440 2008-03-11 10:28 MySet.cmi -rw-r--r-- 1 amd amd 654 2008-03-11 10:28 MySet.cmo -rw-r--r-- 1 amd amd 394 2008-03-11 10:11 MySet.ml -rw-r--r-- 1 amd amd 164 2008-03-11 10:11 MySet.mli - : int = 0 # List.rev [1;2;3] ;; - : int list = [3; 2; 1] #Na segunda alternativa, podemos abrir (importar) primeiro o módulo, para evitar ter de usar prefixos qualificadores. Por exemplo:
# open Sys ;; (* importa um modulo de bibloteca *) # os_type ;; (* usa um nome dum módulo aberto *) - : string = "Unix" # command "ls -l" ;; (* usa um nome dum módulo aberto *) total 24 -rw-r--r-- 1 amd amd 259 2008-03-11 10:26 main.ml -rw-r--r-- 1 amd amd 440 2008-03-11 10:28 MySet.cmi -rw-r--r-- 1 amd amd 654 2008-03-11 10:28 MySet.cmo -rw-r--r-- 1 amd amd 394 2008-03-11 10:11 MySet.ml -rw-r--r-- 1 amd amd 164 2008-03-11 10:11 MySet.mli - : int = 0 #
ocamlc -c MySet.mlsendo gerados os ficheiros "MySet.cmi" e "MySet.cmo".
Exemplo de utilização do módulo aberto MySet:
$ ocaml Objective Caml version 3.09.2 # #load "MySet.cmo" ;; # open MySet ;; # empty ;; - : 'a list = [] # make [1;2;3] ;; - : int list = [1; 2; 3] #Eis o conteúdo do ficheiro "MySet.ml":
(* Module body MySet *) type 'a set = 'a list ;; let empty = [] ;; let rec belongs v l = match l with | [] -> false | x::xs -> x = v || belongs v xs ;; let rec union l1 l2 = match l1 with | [] -> l2 | x::xs -> (if belongs x l2 then [] else [x])@union xs l2 ;; let rec clear l = match l with | [] -> [] | x::xs -> let cl = clear xs in (if belongs x cl then [] else [x])@cl ;; let make l = clear l ;; |
ocamlc -c MySet.mli MySet.mlsendo gerados os ficheiros "MySet.cmi" e "MySet.cmo".
Exemplo de utilização do módulo fechado MySet:
$ ocaml Objective Caml version 3.09.2 # #load "MySet.cmo" ;; # open MySet ;; # empty ;; - : 'a MySet.set = <abstr> # make [1;2;3] ;; - : int MySet.set = <abstr> # clear [1;2;3] ;; Unbound value clear #Eis o conteúdo do ficheiro "MySet.mli":
(* Module interface MySet *) type 'a set (* abstract *) val empty : 'a set val belongs : 'a -> 'a set -> bool val union : 'a set -> 'a set -> 'a set val make : 'a list -> 'a set |
Repare que neste ficheiro interface omitimos a representação interna do tipo 'a set assim como a declaração da função auxiliar clear. Repare ainda na subtileza do tipo da função make (recebe uma lista mas retorna um set).
Quando se esconde a representação interna dum tipo de dados, diz-se que esse tipo é abstracto ou opaco.