Numa linguagem com tipos produto é possível definir, por exemplo, um tipo de dados pessoa constituído por um nome (de tipo string), um ano de nascimento (de tipo int), uma morada (de tipo string), etc.
Os tipos produto do Pascal são os registos.
Os tipos produto do C são as estruturas (mas também se podem designar como registos)
Os tipos produto do Java, Smalltalk e C++ são as classes.
No Fortran, os tipos produto apareceram na versão Fortran 90 com o nome de tipos derivados.
Como é em OCaml?
Os produtos cartesianos do OCaml são exemplos de tipos produto, neste caso ditos não-etiquetados, e também conhecidos por tuplos. Por exemplo, para representar pessoas, pode usar-se em OCaml o seguinte tipos produto não etiquetado:
string * int * stringLiterais: Para exemplificar, eis um valor do tipo anterior:
("João ", 1970, "Lisboa")Construção: Para exemplificar vejamos uma função que muda a morada duma pessoa, criando um tuplo novo:
let moveTo (x,y,_) city = (x, y, city) ;;Processamento: Como se processam tuplos? De duas formas:
let getName p = match p with | (x, _, _) -> x ;;
let getName (x, _, _) = x ;;
não aplicável ao nosso exemplo, por se tratar dum triplo.
type pessoa = { nome:string ; anoNasc:int ; morada:string } ;;Literais: Eis um literal deste tipo:
{ nome = "João" ; anoNasc = 1970 ; morada = "Lisboa" }Construção: Para exemplificar vejamos uma função que muda a morada duma pessoa, criando um registo novo:
let moveTo p city = { nome = p.nome ; anoNasc = p.anoNasc ; morada = city } ;;Processamento: Como se processam registos? De duas formas:
let getNome p = match p with | { nome = x ; anoNasc = _ ; morada = _ } -> x ;;
let getName { nome = x ; anoNasc = _ ; morada = _ } = x ;;
let getNome p = p.nome ;;
As exceções podem ser geradas:
let rec fact x = if x = 0 then 1 else if x>0 then x * fact(x-1) else raise (Arg.Bad "fact") ;; let rec fact x = if x = 0 then 1 else if x>0 then x * fact(x-1) else failwith "fact" ;;
Em OCaml, um tratador de exceções escreve-se usando uma expressão try-with, como se exemplifica de seguida. A expressão exp, no seu interior, diz-se uma expressão protegida.
try exp with Sys_error _ -> exp1 | Division_by_zero -> exp2 | End_of_file -> exp3 ... ;;Como é avaliada uma expressão try-with?
# 4/0;; Exception: Division_by_zero. # open_in "fdsg" ;; Exception: Sys_error "fdsg: No such file or directory".
O programador pode definir novas exceções. A sintaxe da definição duma nova exceção é igual à sintaxe da definição duma variante dum tipos soma.
Exemplos. O primeiro exemplo define uma exceção com argumento; o segundo define uma exceção sem argumento.
exception Stack_overflow of string * int ;; exception I_m_so_out_of_here ;;
Por exemplo, a função fact é parcial porque só está definida para valores não-negativos:
let rec fact n = (* pre: n >= 0 *) if n = 0 then 1 else n * fact (n-1) ;;
Outro exemplo: A função maxList é parcial porque só está definida para listas não-vazias:
let rec maxList l = (* pre: l <> [] *) match l with | [x] -> x | x::xs -> max x (maxList xs) ;;Quando se escreve uma função parcial, espera-se que essa função seja sempre aplicada a argumentos válidos. Mas os programas podem ter erros, como sabemos... Como lidar com esta situação?
Solução clássica: A solução clássica não verifica as precondições e passa a responsabilidade para quem chama a função. A função anuncia na precondição as suas restrições de uso. Estas restrições devem ser cumpridas pelo programador. Se o programador não cumprir as precondições, as consequências ficam a cargo do programador.
Solução do Java: Na linguagem Java, existe a convenção de que as precondições são verificadas à entrada de cada método. Deve ser lançada uma exceção sempre que uma precondição é violada. O programador tem a tarefa de escrever o código de validação, no início de cada método.
Solução do OCaml: Podemos verificar explicitamente as precondições para gerar exceções, mas também podemos não o fazer. Se não o fizermos, na maioria dos casos a linguagem OCaml já trata automaticamente do assunto, gerando exceções quando certas precondições são violadas.
Em OCaml, veja estes dois exemplos com verificação explícita, onde se gera uma exceção e uma mensagem de erro clara por cada precondição violada:
let rec fact n = (* pre: n >= 0 *) if n = 0 then 1 else if n > 0 then n * fact (n-1) else failwith "fact: negative argument" ;; let rec maxList l = (* pre: l <> [] *) match l with | [] -> failwith "maxList: empty list" | [x] -> x | x::xs -> max x (maxList xs) ;;
Mas, repare que, em OCaml, mesmo sem lançar exceções explícitas, as versões originais destas duas funções já geram exceções: a primeira aborta com "Stack overflow" e a segunda gera a exceção Match_failure.
Observação importante: É importante que os programas não disfarcem esses erros. É muito melhor a execução dum programar abortar de forma catastrófica, do que terminar de forma aparentemente normal, produzindo resultados errados. Por isso, convém garantir que uma função parcial não produz realmente qualquer resultado quando aplicada a argumentos inválidos. Gerar uma exceção ou entrar num ciclo infinito, são duas formas que garantem a não produção de resultado.