OCaml, anteriorment denominat[1]Objective Caml és un llenguatge de programació de la família ML, extensió i versió actual del llenguatge de programació Caml,[2] acrònim de "Categorical Abstract Machine Language", creat per Xavier Leroy, Jérôme Vouillon, Damien Doligez, Didier Rémy i altres el 1996, amb construccions d'Orientació a Objectes, successora de l'extensió anterior del mateix llenguatge anomenada Caml Light.
OCaml és un projecte de codi obert impulsat per l'entitat estatal francesa de recerca INRIA (Institut national de recherche en informatique et en automatique).
OCaml és el nou nom oficial (abans era Objective Caml) des del Juliol del 2011,[1] i ha estat adoptat per Microsoft com a base del seu llenguatge funcional F#.[3] No hi ha cap estàndard per al llenguatge; l'única font de compiladors per al llenguatge és el mateix centre de recerca.
Compiladors
OCaml posa èmfasi en el rendiment. Xavier Leroy diu "El compilador OCaml proporciona pel cap baix un rendiment del 50% d'un compilador de llenguatge C"[4] i els bancs de proves mostren que generalment és així.[5]
Es disposa de dos compiladors per a OCaml,
ocamlc a codi intermedi (bytecode) interpretable pel mòdul ocamlrun. (ocamlc -custom genera un executable autònom (stand‑alone) i possibilita l'enllaç amb biblioteques de llenguatge C).
ocamlopt compila a codi nadiu del sistema per a les arquitectures[6]
ocaml és un intèrpret o avaluador d'expressions (ang: Top-Level Read-Eval-Print-Loop) referit habitualment per TopLevel.[7]
OCaml destaca per la gestió de memòria amb recuperació incremental de curta durada.
>> .. about soft real-time performance .. GHC's stop-the-world GC will incur prohibitively long pause times for this kind of application (soft real-time), orders of magnitude longer than the pause times of OCaml's incremental GC.[8][9]
Ocaml implementa un gestor de memòria dinàmica (ang:garbage collector) de dues generacions
la generació jove quan arriba al límit fa servir el sistema "stop and copy" (aturar i copiar) de dos espais amb destinació a la generació vella.
la gen. vella quan arriba al límit fa servir el sistema "mark and sweep" (marcar i escombrar) de manera incremental amb compactació opcional
El caràcter incremental del recol·lector a la generació més vella, possibilita un temps reduït de les pauses. A cada passada de la generació jove (minor collection) fa una part de la feina sobre la generació vella (major collection).
Fent una prova amb el programa del tutorial de la referència[10] esmentat al paràgraf "The GC module", es comprova que les pauses per recuperació es poden comptar per poques dècimes de mil·lisegon malgrat que poden augmentar ocasionalment degut a la latència o espera de commutació de fil del planificador del nucli.
(<0.0002 segons amb ocamlc i <0.0001 amb ocamlopt mesurat sobre un Intel Atommodel 330 amb LinuxUbuntu 10.04 "preempt"[11] de 64 bits i 2 GB de RAM i amb el compilador recompilat a partir dels fonts modificats per calcular, en les manques de memòria dinàmica caml_check_urgent_gc, la pausa màxima havent creat totes les estructures, és a dir, partint en haver completat la primera recol·lecció de la generació vella).
l'alfabet del codi queda limitat als caràcters ASCII (0-127) (els únics que ocupen un sol octet en UTF-8). Les tires de text literals amb caràcters no anglosaxons (en codificació UTF-8 ocupen més d'un octet), poden donar resultats inesperats si es tracten amb el tipus String (String.length "Món" = 4 !! (4 bytes en UTF-8)) en comptes de ser tractats amb tipus més adients. Vegeu #tipus Unicode i exemple
Les clàusules acaben en doble punt-i-coma, per distingir-lo del punt-i-coma separador de la seqüència d'instruccions en una definició (lligam).
let nom_definit = expr; expr; ..; expr ;;
(* definició de tipus *)
type tipus = Constructor1 of tipus_dels_components | Constructor2 of tipus_dels_components | ... ;;
exception Excepció of tipus_dels_components ;;
(* dins una signatura *)
val nom_definit : expr_de_tipus
(* dins una def. d'objecte *)
val [mutable] nom_de_camp = expressió
grafia
Precedits d'un apòstrof
variables de tipus
Precedits d'una cometa revessa (accent greu + espai)
constructors de tipus variant
Començant per majúscula
Constructors de tipus
Símbols d'enumeracions
Començant per minúscula
la resta.
comentaris
(* comentari multilínia
no existeix el comentari fins a fi de línia
(*
comentari niuat
*)
*)
(** comentari d'autodocumentació
*)
nova sintaxi
De manera opcional es proposa una revisió de la sintaxi per solucionar dificultats amb l'analitzador sintàctic.[14][15]
compilació amb la nova sintaxi
ocamlc -pp camlp4r programa.ml
nova sintaxi a l'intèrpret
ocaml
# load "camlp4r.cma";;
tipus
tipus bàsics
type unit (* tipus buit, el de les expressions d'efectes col·laterals que no retornen cap valor
literals: ()
*)
() com a únic paràmetre en una funció indica que cal recalcular-ne el valor a cada invocació
type bool (* tipus booleà
literals: true, false
ops: not, &&, || (''or'' i ''&'' han quedat obsolets)
*)
type int (* sencers 31 o 63 bits segons la màq.<ref>[http://caml.inria.fr/pub/docs/manual-ocaml/libref/Pervasives.html#6_Integerarithmetic Aritmètica de sencers, 31 o 63 bits]{{en}}</ref> *),
literals: 5, -5, 6-5 (* la op. menys unària es fa amb el signe '-'
contràriament a SML *)
ops: + - * / mod abs max_int min_int
<, <=, >, >=, = (igual valor), <> (not =), == (igualtat referencial),<ref>[http://caml.inria.fr/pub/docs/manual-ocaml/libref/Pervasives.html#VAL(%3D%3D) Igualtat referencial (==) respecte Igualtat estructural (=)]</ref> != (not ==)
land, lor, lxor, lnot, lsl, lsr, asr
No hi ha literals en bases hexa, octal, binari, però la conversió int_of_string els pot llegir d'una cadena amb el prefix "0x", "0o", "0b" respectivament.
type float (* coma flotant de 64 bits [[IEEE 754]] *)
literals: 0.44 1.2E2 5. (* la part sencera no pot ser buida, la decimal sí !! *)
ops: +. -. *. /. ** ceil floor max_float min_float int_of_float float_of_int
(* els operadors numèrics de floats que coincideixen amb els de sencers
porten un puntet diferenciador *)
(* no generen excepcions en cas de sobreiximent o div. per zero
''classify_float'' torna un de
(FP_normal | FP_subnormal (valors IEEE754 amb repr. ''denormal'' inf. als normals
per evitar div.per.zero)
| FP_zero | FP_nan (0/0) | FP_infinity) Vegeu ref.<ref>[http://caml.inria.fr/pub/docs/manual-ocaml/libref/Pervasives.html#6_Floatingpointarithmetic Aritmètica de Coma flotant] {{en}}</ref>*)
<, <=, >, >=, =, <>, ==, !=, classify_float
(* tuples (tipus producte), separador en els valors la coma sense parèntesis també val, la coma n'és el constructor *)(2,3.5):int*floatfst(2,3.5)=2;;snd(2,3.5)=3.5;;typela_meva=int*float
tipus producte - registres
(* registres, separador el punt i coma *){a=2;b=3.5}:{a:int;b:float}(* registres amb camps mudables *)typepersona={nom:string;mutableedat:int}letp={nom="Joan";edat=30};;letaniversari(p:persona)=p.edat<-p.edat+1;;(* actualització funcional (clonar i actualitzar) *)letq={pwithnom="Josep"};;
tipus suma (unió discriminada)
(* unió discriminada per etiquetes (constructors) *)typenombre=Sencerofint|Realoffloat;;letllista_de_nombres=[Sencer10;Real2.5];;
tipus polimòrfics
(* tipus polimòrfics, 'a : el prefix apòstrof indica variable de tipus cas de més d'una variable, es posen en tupla *)(* opcionalitat de valor, per a resultats de funcions definides parcialment *)type'aoption=None|Someof'a;;(* amb dues variables *)type('a,'b)a_o_b=Aof'a|Bof'b;;typesencerA_o_realB=(int,float)a_o_b;;(* tipus recursius *)type'aarbre=Fullaof'a|Brancaof'a*'aarbre*'aarbre;;leta=Fulla2;;letb=Branca(5,a,a);;
tipus amb allotjament lineal
tipus string
Els caràcters ocupen un octet (valors 0..255). (Per a tires de caràcters Unicode vegeu #tipus Unicode)
Vegeu[17]
typestring(* text *)(* literals: "abc" ops: "abc" ^ "def" = "abcdef" *)letstr="abc";;String.substr02="ab";;(* subtira amb inici i llargada *)str.[0];;(* obtenir caràcter a la posició x *)str.[1]<-'m';;(* estableix caràcter -- les tires són mudables *)(* (==) igualtat referencial (indica si són la mateixa instància) *)("abc"=="abc")=false;;("abc"="abc")=true;;(* (=) igualtat estructural *)
(* vectors / arrays *)letarr=[|1;2;3|];;(* literals *)letarr=Array.make201.0;;(* 20 elements amb el valor *)letarr=Array.init20(funi->2*i);;(* 20 elements segons funció *)letelem=arr.(0);;(* obtenir elem., el primer índex és el 0 *)arr.(2)<-6;;(* establir elem.*)
llistes homogènies
(* llistes, separador el punt i coma *)[1;2;3];;(* literals *)[1,2;3,4]=[(1,2);(3,4)];;(* la coma fa el parell, el separador de llistes és el; *)1::[2;3];;(* op. Cons -- - : int list = [1; 2; 3] *)[1;2]@[3];;(* concatena -- - : int list = [1; 2; 3] *)List.hd[1;2;3]=1;;(* hd (abreviació de "head" : cap) *)List.tl[1;2;3]=[2;3];;(* tl (abreviació de "tail" : cua) *)
La finalitat és discriminar els elements d'una col·lecció per encaix de patrons de l'etiqueta, que ha de començar per una cometa revessa, feta amb l'accent greu seguit d'espai, i un identificador que comenci per majúscula
Cada etiqueta (constructor) s'acompanya d'un sol valor (component) sempre del mateix tipus
El tractament serà per encaix de patrons segons l'etiqueta
Les llistes de variants es poden comparar, encara que continguin elements amb constructors diferents.
letllista_d_etiquetats=[`Pomes10;`Sucre2.5];;(* val llista_d_etiquetats: [> `Pomes of int | `Sucre of float ] list *)letnovallista=llista_d_etiquetats@[`Ous2];;(* concatenació *)(* les llistes etiquetades es poden comparar, no hi ha queixa de tipus malgrat difereixin en etiquetes *)(novallista=llista_d_etiquetats);;(* - : bool = false *)
letpi=4.*.atan1.;;(* encaix de tuples i registres *)let(v_a,v_b)=(1,2);;(* val v_a : int = 1 val v_b : int = 2 *)letv_a,v_b=1,2;;(* sense parèntesi també val: la coma és el constructor de tupla *)typeel_meu_tipus={a:int;b:float};;(* registres *)letel_meu={a=2;b=3.5};;let{a=v_a;b=v_b}=el_meu;;(* val v_a : int = 2 val v_b : float = 3.5 *)
restricció de tipus
''expr'' : ''tipus''
(* (#) restricció de paràmetres a ''class type'' (signatures d'objectes
i ''interfaces'') *)
''paràmetre'' : # ''super_classe_o_interface''
(* (:>) coerció a tipus de classe menys especialitzat (''up-cast'')
(superclasses i ''class type''s) -- secció [[#herència|Herència]] i següents *)
''expr_subtipus'' :> ''super_tipus_de_classe''
alternatives
if ''expr'' then ''expr'' else ''expr'' ;;
match ''expr'' with
''patró'' when ''expr'' -> ''expr''
| ''patró'' | ''patró'' | ''patró'' when ''expr'' -> ''expr''
...
;;
declaracions locals
amb àmbits successius de visibilitat
let resultat = let ''declaració'' in (* àmbit més extern *)
let ''declaracio2'' in (* àmbit intermedi *)
''expr'' ;; (* àmbit intern *)
funcions
letsumaxy=x+y;;(* val suma : int -> int -> int = <fun> *)(* funció recursiva, ''rec'' permet fer referència al nom de la funció *)letrecfactn=matchnwith0->1|m->m*fact(m-1);;(* val fact : int -> int = <fun> *)
(* lligam amb funció anònima, clàusula function definició per encaix de patrons (pattern-matching)
clàusula fun definició per paràmetres[23]
*)
letrecfact=function|0->1|n->n*fact(n-1);;(* definició simultània (''and'') amb crides circulars, ''rec'' permet fer referència als identificadors definits *)letrecparell=function|0->true|n->senar(n-1)andsenar=function|0->false|n->parell(n-1);;(* val parell : int -> bool = <fun> val senar : int -> bool = <fun> *)
(* composició de funcions, predefinit: ''compose'' *)(* no hi ha cap operador específic per la composició *)letcomposefg=functionx->f(g(x));;
La composició de funcions en un llenguatge estricte, genera estructures intermèdies i és preferible resoldre el problema de la desforestació fusionant manualment les operacions en una funció amb un sol bucle.
La biblioteca Batteries defineix (-|) com a operador de composició.[24]
pas de paràmetres etiquetat
per nom
(* amb pas de paràmetres per nom (l'ordre dels param. es pot alterar) ~nomparam:argument o ~nomparam com a abrev. de ~nomparam:nomparam ~ es fa amb les tecles AltGr+4 *)letrecfact~num:n=matchnwith0->1|m->m*fact~num:(m-1);;
L'opcio de compilació -nolabels requereix l'ordenació estricta dels paràmetres a la crida (ignora les etiquetes de paràmetres no opcionals).[25]
paràmetres opcionals
paràmetres opcionals
letsalt?incrx=(* ? prefixa el param. opcional formal *)matchincrwith|None->x*2(* cas de None: el param. actual no hi era *)|Somey->x+y(* cas de Some: el param. actual sí que hi era *);;(* val salt : ?incr:int -> int -> int = <fun> *)(* ús *)let()=letr=salt~incr:12(* ~ prefixa el param. opcional actual *)inprint_intr;print_endline"";;
Requeriment: un paràmetre opcional ha d'ésser seguit, com a mínim, d'un paràmetre anònim (no etiquetat), el qual tanca la possible seqüència de paràmetres opcionals.[26]
paràmetres opcionals amb valor per omissió
letsalt?(incr=1)x=x+incr;;(* val salt : ?incr:int -> int -> int = <fun> *)
operadors
Permet l'ús en prefix (entre parèntesis) o bé infix dels operadors binaris.[27]
(* definició *)let(++):'alist->'alist->'alist=funxy->x@y;;(++)[1;2][3;4](* ús en posició prefix *)[1;2]++[3;4](* ús en posició infix *)
El caràcter inicial de l'operador determina la seva precedència (per exemple els definits començant per '*' tenen la mateixa precedència dels operadors multiplicatius). Vegeu Taula de Precedències i associativitat dels operadors[28]
excepcions
exceptionELlistaBuidaofstring;;letobtenir_capllista=matchllistawith[]->raise(ELlistaBuida"dins obtenir_cap")|(cap::_)->cap;;letla_meva_llista=[];;letres:'aoption=try(Some(obtenir_capla_meva_llista))with|ELlistaBuidamsg->print_endline("ELlistaBuida: tururut "^msg);None|_->print_endline"altra excepció";None;;(* funcions per llançar Excepcions típiques *)failwith"motiu de la fallada"(* llança exc. Failure *)invalid_arg"rutina rut, segon paràmetre"(* llança exc. Invalid_argument *)
Programació imperativa
La que modifica l'estat.
registres amb camps mudables
typepersona={nom:string;mutableedat:int};;letjoan={nom="Joan";edat=30};;letaniversari(p:persona)=p.edat<-p.edat+1;;(* retorna unit *)aniversarijoan;;print_intjoan.edat;;
Referències
ref introdueix una referència (punter) a l'allotjament d'un altre objecte
(!) és l'operador invers que ens revela el valor de l'objecte referit per la variable
(:=) assigna un valor a l'objecte referit per la variable
leta=ref5;;leta:=!a+1;;(* assignació -- qualsevol assignació retorna ''unit'' com a valor d'expressió*)letresultat=a:=!a+1(* retornaria ''unit'' *);!a(* ara retornarà el valor allotjat *);;
efectes laterals
let_=print_endline"abc";;(* el patró comodí '_' obvia el resultat *)let()=ignore(expressió);;(* ignore descarta el resultat, retorna Unit *)leta=print_string"bla bla bla\n";"nou_valor a retornar";;(* efectes laterals i resultat a la mateixa clàusula *)letimprimeix_llista_de_sencers_amb_separadorllistasep=print_int(List.hdllista);(* imprimeix el cap *)(* imprimeix la resta d'elements prefixant-hi el separador *)(* List.iter : ('a -> unit) -> 'a list -> unit *)List.iter(Printf.printf"%s%d"sep)(List.tlllista)(* aplicació parcial de ''(printf "%s%d")'' deixant un dels paràmetres per al mapeig amb List.iter *);;imprimeix_llista_de_sencers_amb_separador[1;2;3]", ";;
letsequenciai=ifi<=4thenSomeielseNone;;letcorrent=Stream.fromsequencia;;letfi=reffalse;;whilenot!fidomatch(Stream.peekcorrent)with(* Stream.peek consulta el primer *)|Somev->let_=Printf.printf"\n%d"vinStream.junkcorrent(* Stream.junk escapça el corrent (treu el primer) *)|None->fi:=truedone;;
Orientació a objectes (aspectes imperatiu i funcional)
Vegeu[29] Facilita objectes immediats d'una sola instància i classes d'objectes immutables o mudables.
Els camps (clàusula val) són privats, accessibles només per als mètodes propis o de classes derivades.
Els mètodes poden contenir el modificador private.
La clàusula object admet la declaració d'una ref. a la instància, anomenada habitualment self com a object (self) ... end
Poden incorporar una clàusula d'inicialització (initializer)
Objectes d'una sola instància (ang: singleton) ometent la clàusula class
objectes immediats (d'una sola instància)
letobj=objectvalfactor=5(* camp de la instància (accés privat) *)methodmult_per_factorx=x*factorend;;obj#mult_per_factor4;;-:int20
classes d'objectes
tots els camps són d'accés privat
new en genera una instància.
classrectangle(x:float)(y:float)=objectvalarea=(x*.x)+.(y*.y)(* camp de la instància (accés privat) *)methodobtenir_area=areaend;;letnou_rect=newrectangle3.4.;;nou_rect#obtenir_area;;-:float25.0
objectes mudables
Incorporant el qualificatiu mutable als camps
Pot incorporar una clàusula d'inicialització initializer que el generador new executa.
El desallotjament automàtic fa innecessaris els destructors. Malgrat això, es pot registrar una funció com a finaliser al recollidor de brossa.[30]
classpila_de_sencers=objectvalmutablela_llista:intlist=[](* camp de la instància, accés privat *)methodapilax=la_llista<-x::la_llistamethodlleva=letprimer=List.hdla_llistainla_llista<-List.tlla_llista;primermethodprivatemida=(* privat: accessible només des de la mateixa classe *)List.lengthla_llistamethodes_buida=(la_llista=[])initializer(* new com a constructor l'inicialitza *)print_endline"pila inicialitzada"end;;letp=newpila_de_sencers;;(* "pila inicialitzada" val p : pila_de_sencers = <obj> *)(* paràmetres de funció-- let nom_funció (param : classe_tal) -- sense prefix, el tipus del param. ha de ser exactament classe_tal-- let nom_funció (param : #classe_tal) -- cas de prefix '#', el tipus del param. pot ésser subtipus de classe_tal* )let omple (s: #pila_de_sencers) = for i = 1 to 5 do s#apila i done;;let buida (s: #pila_de_sencers) = while not s#es_buida do Printf.printf "Hem llevat %d de la pila.\n" s#lleva done;;
Execució a l'intèrpret ocaml
# p ;;
- : pila_de_sencers = <obj>
# omple p ;;
- : unit = ()
# buida p ;;
Hem llevat 5 de la pila.
Hem llevat 4 de la pila.
Hem llevat 3 de la pila.
Hem llevat 2 de la pila.
Hem llevat 1 de la pila.
- : unit = ()
#
actualització funcional dels objectes immutables
La construcció {< ... >} permet l'actualització funcional en una nova instància, retornant-ne un clon actualitzat.[31]
classpassa_anys(edat_ini:int)=objectvaledat=edat_inimethodaniversari={<edat=edat+1>}(* clonar i actualitzar *)methodimprimeix=print_string"edat: ";print_intedat;print_newline()end;;letjoan=newpassa_anys10;;let_=joan#aniversari#imprimeix;;
classes abstractes
Els mètodes es poden fer abstractes (pendents d'especificar en classes derivades) amb la qualificació "virtual". Les classes amb mètodes virtuals han de ser declarades virtuals.
classvirtualpunt_abstractex_init=object(self)valmutablevirtualx:intmethodvirtualget_x:intmethodget_dist=self#get_x-x_initend;;classpointx_init=objectinheritpunt_abstractex_initvalmutablex=x_initmethodget_x=xend;;(* tipus resultant: class point : int -> object val mutable x : int method get_dist : int method get_x : int end *)
herència
Les classes poden tenir herència múltiple. En cas de col·lisió de noms de mètode heretats, preval l'heretat darrer.
(* provant a l'intèrpret ocaml *)classpunt(x_inicial:float)(y_inicial:float)=object(self)valmutablex=x_inicialvalmutabley=y_inicialmethoddesplassadelta_xdelta_y=(* la ç de desplaça no s'admet als ident. UTF-8 (vegeu lèxic) *)x<-x+.delta_x;y<-y+.delta_ymethodimprimeix=Printf.printf"posició %.2f %.2f\n"xyend;;typecolor=Verd|Roig|Blau;;letstring_of_color=functionVerd->"Verd"|Roig->"Roig"|Blau->"Blau";;classacolorit(c_inicial:color)=object(self)valmutablecolor=c_inicialmethodpintanou_color=color<-nou_colormethodimprimeix=print_endline("color: "^(string_of_colorcolor))end;;classpunt_acolorit(x:float)(y:float)(c:color)=object(self)inheritpuntxyassuper_puntinheritacoloritcassuper_acoloritmethodimprimeix=super_punt#imprimeix;super_acolorit#imprimeixend;;letp_acolorit=newpunt_acolorit1.02.0Verd;;p_acolorit#imprimeix;;(* posició 1.00 2.00 color: Verd - : unit = () *)
Interfícies d'objectes
A l'estil dels Interface de Java, designen el conjunt de tipus que implementen la signatura descrita amb class type.
(* seguint l'anterior codi a l'intèrpret ocaml *)classtypeimprimible=objectmethodimprimeix:unitend;;letimprim_test(obj:#imprimible)=obj#imprimeix;;(* val imprim_test : #imprimible -> unit = <fun> *)(* El tipus de punt_acolorit és subtipus de imprimible perquè n'inclou els mètodes i camps (en cas que n'hi hagi). *)imprim_testp_acolorit;;(* posició 1.00 2.00 color: Verd - : unit = () *)letp_ras=newpunt1.51.5;;(* punt ras *)imprim_testp_ras;;(* posició 1.50 1.50 - : unit = () *)
Col·leccions de valors amb funcionalitat comuna
Per exemple, llistes amb elements d'aquells tipus que implementin una funcionalitat determinada per una interfície. Cal fer un up-cast (caracterització a tipus menys especialitzat) de cadascun dels elements al tipus de la interfície.
(* seguint les seccions anteriors *)(* caracterització a tipus menys especialitzat (''up-cast''), en aquest cas un interface *)letcast_a_imprimiblecp=(cp:>imprimible);;(* val cast_a_imprimible : #imprimible -> imprimible = <fun> *)(* col·leccions homogènies *)letcoleccio_d'imprimibles=[cast_a_imprimiblep_ras;(p_acolorit:>imprimible)];;(* - : imprimible list *)let_=List.iterimprim_testcoleccio_d'imprimibles;;(* iteració de imprim_test a la llista *)
llistes obertes d'elements etiquetats
Unió discriminada oberta (sense definició prèvia de tipus)
classparell_de_sencers(x:int)(y:int)=objectmethodget_x=xmethodget_y=yend;;letparelleta_de_sencers=newparell_de_sencers53;;class['a,'b]parell_de_tipus_generic(x:'a)(y:'b)=objectmethodget_x=xmethodget_y=yend;;letparelleta_de_dos=newparell_de_tipus_generic53.2;;(* val parelleta_de_dos : (int, float) parell_de_tipus_generic = <obj> *)(* herència *)class['a,'b]parell_de_classe_generica_derivada(x:'a)(y:'b)=objectinherit['a,'b]parell_de_tipus_genericxy(* ... *)end;;letparelleta_del_derivat=newparell_de_classe_generica_derivada7.59.5;;(* funció que retorna resultat de tipus diferent segons la instanciació de l'objecte de la classe genèrica *)letobtenir_x(p:('a,'b)#parell_de_tipus_generic)=p#get_x;;obtenir_xparelleta_de_dos;;-:int5obtenir_xparelleta_del_derivat;;-:float7.5
restriccions als paràmetres de funcions i de classes
classtypeimprimible=(* interfície "imprimible" (signatura) *)objectmethodimprimeix:unitend;;(* als paràmetres de funcions *)lettest_impressio(obj:#imprimible)=obj#imprimeix;;(* als paràmetres de les classes *)classclasse_amb_component_imprimible(obj_ini:#imprimible)=object(self:'c)constraint'c=#imprimible(* requeriment que la classe implementi l'interface *)valobj=obj_inimethodimprimeix=obj#imprimeixend;;(* als paràmetres de tipus de classes genèriques *)class['a]classe_genèrica_amb_component_imprimible(obj_ini:'a)=objectconstraint'a=#imprimiblevalobj=obj_inimethodimprimeix=obj#imprimeixend;;
constructors de còpia
Oo.copy crea una còpia superficial (la que no duplica els valors referits per punters ref).[33]
Un fitxer de codi sense clàusula module es considera un mòdul en si mateix i el seu nom de mòdul és el del fitxer amb la primera lletra majúscula.
Per fer-ne servir un identificador cal prefixar-lo: "Nom_fitxer.ident"; per incorporar tots els seus identificadors a l'àmbit actual s'utilitza la clàusula open
openNom_fitxer(* importa l'espai de noms del mòdul Nom_fitxer *)(* o també *)letopenNom_fitxerin(* importació d'àmbit local, versió 3.12 *)...
La clàusula module introdueix un submòdul que s'adreça externament com a Nom_fitxer.Nom_modul
(* fitxer nom_fitxer.ml *)moduleIntern=structletident=3end;;(* des de dins del fitxer, ens hi referirem per Intern *)print_endline("resultat: "^(string_of_intIntern.ident));;(* des d'un altre fitxer : *)openNom_fitxer.Intern
Compilació
En una compilació els fonts s'han d'especificar en ordre tal que si Mòdul_A crida Mòdul_B, Mòdul_B ha de precedir Mòdul_A.
En cas que les referències entre mòduls fossin circulars, cal generar i compilar primer els fitxers de signatura (secció següent).
(* compilació a codi intermedi "bytecode" (per executar la tasca
caldrà la presència de l'intèrpret de bytecode ocamlrun *)
ocamlc modul_B.ml modul_A.ml -o nom_tasca
(* En executar la tasca en bytecode, crida a l'intèrpret *)
./nom_tasca arg1..argn
(* execució des de l'intèrpret *)
ocamlrun nom_tasca arg1..argn
.
També es pot compilar a fitxer biblioteca, especificant -a
(* compilació a biblioteca de codi intermedi (bytecode) *)
ocamlc -a modul_B.ml modul_A.ml -o nom_llib.cma
(* compilació a biblioteca de codi nadiu *)
ocamlopt -a modul_B.ml modul_A.ml -o nom_llib.cmxa
Compilació amb biblioteques del sistema de paquets
per exemple la biblioteca de tractament de tires de caràcters Unicode del paquet "Camomile"[20] incorporada amb el gestor de paquets Findlib.[34] o la distribució en codi font GODI[35]
ocamlfind,[36] interfície del gestor de paquets, proporciona directoris, biblioteques i opcions al compilador segons la info. del fitxer META del paquet.[37]
a Linux amb la codificació descrita a la variable d'entorn LANG = ca_ES.UTF-8
./mon
UTF8.length 'Món' : 3
String.length 'Món' : 4
"CamomileLibrary" NO és el nom del fitxer biblioteca, és un mòdul generat amb l'opció -pack del compilador (empaqueta com a submòduls els mòduls esmentats com a paràmetres).[38][39] Aquest sistema no és regla general dels paquets.
(* extracte del Makefile del paquet Camomile *)
camomileLibrary.cmo camomileLibrary.cmi : $(OBJECTS)
$(OCAMLC) $(BOPTIONS) -pack -o camomileLibrary.cmo $(OBJECTS) $(INCLUDES)
Generació d'intèrprets personalitzats
OcamlMkTop[40] permet crear avaluadors d'expressions OCaml (intèrprets o Toplevels en argot OCaml) amb mòduls i biblioteques precarregats.
A voltes dona millor informació d'errors en cas de manca d'adequació de crides Ocaml a biblioteques del sistema subjacent.
Seqüència d'especificacions de tipus dels identificadors exportats per un mòdul.
Generació partint del codi font
En cas que interessi declarar d'accés privat (d'ús intern) algun identif. d'un mòdul-fitxer, generarem un fitxer amb la seva signatura amb la comanda següent, redirigint la sortida:
ocamlc -i fitxer_a.ml > fitxer_a.mli
Com que només s'exporten els identificadors presents a la signatura, editarem el .mli eliminant les declaracions que volem d'accés privat, i el compilarem amb ocamlc, generant el .cmi (interfície compilada)
(* cas de compilació a codi nadiu *)
ocamlc -c fitxer_a.mli (* genera interfície compilada .cmi *)
ocamlopt -c fitxer_a.ml (* genera objecte de codi nadiu .cmx *)
ocamlopt fitxer_a.cmx principal.ml -o nom_tasca (* genera executable nadiu *)
(* cas de compilació a codi intermedi (bytecode) *)
ocamlc -c fitxer_a.mli fitxer_a.ml (* genera interfície compilada .cmi i objecte bytecode .cmo *)
ocamlc fitxer_a.cmo principal.ml -o nom_tasca (* genera executable bytecode *)
amb signatura explícita
module NomMòdul : Signatura
(:) equival a l'adscripció opaca del ML Estàndard quedant els tipus tan visibles com ho siguin a la signatura, i quedant d'accés privat els elements de l'estruct. no presents a la signatura.
A partir de v3.12: herència (include) múltiple i substitució de tipus eliminant la instrucció type interna de les signatures incloses.[41]
moduletypeSigDelMeuComponent=sigtypeuincludeImprimiblewithtypet:=u(* v. 3.12 (substitució del tipus eliminant la instr. type interna) *)includeOrdenablewithtypet:=uend
Els comentaris entre (** i *) es poden fer servir per generar un fitxer d'autodocumentació mitjançant l'eina ocamldoc[44]
Els comentaris entre (* i *) no entren a l'autodocumentació.
S'hi poden incloure camps estàndard per documentar el mòdul i els paràmetres de les definicions. (els tipus no cal posar-los-hi que ja surten automàticament) com ara
@author text
@version text
@param roldescripció (* no cal posar-hi tipus, ja l'obté de la def. *)
@return descripció
@raise Excepció descripció
@see url (vegeu ..)
@since text (des de versió tal)
@deprecated text (obsolet s'avisa que aquesta funció ...)
Vegeu-ne l'ús a l'exemple més avall. Un exemple, amb el fitxer de signatura i sense, seria
ocamldoc -html modul.ml
o bé
ocamldoc -html modul.ml -intf modul.mli
Generaria els fitxers de documentació Modul.html i type_Modul.html
(* fitxer arrenca.ml, utilitza mòduls Sys i Arg *)(* prog. d'anàlisi de línia d'ordres *)letnomprog=Sys.argv.(0);;(* argc = Array.length Sys.argv *)letusage_msg=Printf.sprintf"Ús: %s -v -o sortida --num 10 fitxer_a fitxer_b\n"nomprog;;(* variables per a opcions i paràmetres *)letref_flag_v=reffalse;;letref_opcio_o=ref"";;letref_opcio_num=ref0;;letref_fitxers=ref([]:stringlist);;(* especificació d'opcions: llista de (opció, comanda : Arg.spec, text_d'ajuda) *)letspec_opcions=[("-v",Arg.Setref_flag_v,"versió");("-o",Arg.Set_stringref_opcio_o,"output");("--num",Arg.Set_intref_opcio_num,"numeric")];;(* paràmetres posteriors a les opcions, apilar-los a !ref_fitxers, quedaran en ordre invers *)leten_llegir_argarg=ref_fitxers:=arg::!ref_fitxers;;(* capgira la llista per recuperar-ne l'ordre *)letobtenir_args_de_comanda()=List.rev!ref_fitxersletprocessa_argarg=Printf.printf"tracta param. %s\n"arg;;letrun=Arg.parsespec_opcionsen_llegir_argusage_msg;Printf.printf"flag_v: %B\n"!ref_flag_v;Printf.printf"opcio_o: %s\n"!ref_opcio_o;Printf.printf"opcio_num: %d\n"!ref_opcio_num;List.iterprocessa_arg(obtenir_args_de_comanda())(* aplica funció a params. retornant unit *);;(* fi de fitxer *)
GODI[35] és un rebost de paquets de codi font que en facilita la instal·lació i actualització compilant a destinació.
Incorpora un programa anomenat godi_console que proporciona una interfície de menús en una consola de comandes, per a la selecció, instal·lació i actualització dels paquets[52]
A l'hora d'instal·lar un paquet, cal fer primer l'operació test per comprovar si manquen paquets dels quals depèn, que haurem d'assenyalar "per instal·lar" individualment.
Per anul·lar les operacions GODI marcades, si no ens en sortim, cal cercar l'arxiu "build/wishes.txt" de la instal·lació GODI i buidar-lo o eliminar-lo.
Els paquets de distribució Godi poden tenir un nom lleugerament diferent del nom amb Findlib (per exemple godi-camomile per al paquet camomile). Per utilitzar-los en compilació cal fer servir el nom original del paquet (per exemple camomile) i el sistema ja descrit per al Findlib.
Compartint recursos
En cas d'executar més d'un programa ocaml alhora, de cara a minimitzar l'ús de memòria hi ha les següents opcions:
Un únic gestor de memòria
La compilació a bytecode permet tenir una única instància del gestor de memòria i interfase amb el sistema operatiu. el programa ocamlrun interpreta el codi intermedi.[53]
Relligat dinàmic amb biblioteques
Vegeu ref.[54]ocamlmklib genera biblioteques estàtiques i dinàmiques partint d'objectes i biblioteques de codi OCaml i codi C.[55]
Portabilitat
El codi intermedi (bytecode) pot córrer a, pràcticament, qualsevol plataforma compatible amb POSIX amb un compilador compatible amb ANSI-C.
La llista oficial de plataformes suportades aquí.[56]
JoCaml
JoCaml[57] és una extensió del llenguatge que implementa l'àlgebra de processos CSP anomenada "càlcul Join"[58] i facilita la concurrència per pas de missatges amb sincronització per encaix de patrons per a sistemes distribuïts amb migració de processos.
OCaml sobre JavaVM
Amb el programari OCamlJava[59][60] Requereix JVM >= 1.6
Execució de codi intermedi OCaml (projecte Cadmium)
ocamlc hola.ml -o hola # compilació a codi intermedi
java -jar $OCAMLJAVA/ocamlrun.jar hola # execució a JVM de bytecode ocaml
Cadmium per a l'Android: joecaml[61] constitueix una reimplementació per a una versió més antiga de Java, la 1.5 compatible amb Android. (Cadmium de OCamlJava requereix Java 1.6)
Compilació a codi intermedi JVM (projecte Cafesterol)
(amb "-standalone" incorpora totes les biblios necessàries a l'arxivador .jar de sortida)
OpenMirage[69] és una implementació per desenvolupar micronuclis servidors d'aplicacions Núvol d'internet sobre l'hipervisorXen (sense sistema operatiu).
Exemples
Ent./Sortida
(** fitxer volca_fitxer.ml (Autodocumentació) especifiquem els tipus de sortida de les rutines per assegurar-ne la comprovació a les diferents branques, els tipus dels params. d'entrada els dedueix de les operacions involucrades .- les operacions sense prefix de mòdul les trobareu al Pervasives @author jo i en pitus @version 1.0 *)(** llegeix una línia i l'escriu @param inp canal d'entrada @raise End_of_file fi de fitxer *)letvolca_linia(inp:in_channel):unit=letlínia=input_lineinpinprint_endlinelinia;;(** llegeix d'un canal d'entrada @param inp canal d'entrada *)letvolca_canalinp:unit=try(whiletruedovolca_liniainpdone)withEnd_of_file->print_endline"\n-- fi de fitxer"|_->print_endline"\n-- altra excepció";;(** obre el fitxer comprovant error @param nom_fitxer nom del fitxer @return possible canal d'entrada *)letobre_fitxernom_fitxer:in_channeloption=try(letinp=open_innom_fitxerinSomeinp)with_->let_=Printf.printf"Error en obrir fitxer %s per llegir\n"nom_fitxerinNone;;(** volca el fitxer especificat si és que existeix @param nom_fitxer nom del fitxer *)letvolca_fitxernom_fitxer:unit=ifnot(Sys.file_existsnom_fitxer)thenPrintf.printf"El fitxer de nom %s no existeix\n"nom_fitxerelseletpossible_inpc=obre_fitxernom_fitxerinmatchpossible_inpcwithNone->()|Someinpc->volca_canalinpc;close_ininpc;;(** comprova que la tasca té un argument i el pren de nom de fitxer a volcar *)letmain=letnom_prog=Sys.argv.(0)inifArray.lengthSys.argv<>2thenPrintf.printf"ús: %s nom_fitxer\n"nom_progelsevolca_fitxerSys.argv.(1);;
Compila, executa, genera interfície (signatura) i genera fitxers d'autodocumentació (de la implementació nom-amb-primera-lletra-majúscula.html i de la interfície type_nom-amb-primera-lletra-majúscula.html)
Estalvien espai en el tractament de llistes llargues o infinites.
(* tipus de llista d'avaluació tardana (ang:lazy evaluation) *)type'anode_t=|Nil|Consof'a*'azlist_tand'azlist_t='anode_tLazy.t;;(** generador tardà de m elements; ''lazy'' deixa la generació en suspens *)letrecgenera_llista(m:int):'azlist_t=lazy(matchmwith|0->Nil|n->Cons(n,genera_llista(n-1)));;(** fold_left_tardà: fold_left sobre llista d'avaluació tardana; ''Lazy.force'' força l'avaluació del generador de la llista obtenint-ne 1 elem. *)letrecfold_left_tardàfacum(llista:'azlist_t)=match(Lazy.forcellista)with|Nil->acum|Cons(x,xs)->fold_left_tardàf(facumx)xs;;letop_binaria=fun(a:float)(b:int)->a+.sqrt(float_of_intb);;(* qualsevol que interessi *)let_=Printf.printf"resultat: %.2f\n"(fold_left_tardàop_binaria0.(genera_llista100000));;
Interfície d'entorn gràfic -- Hola món a GTK amb Glade
Glade és una eina de disseny d'interfícies gràfiques d'usuari per a l'entorn GTK que desa el disseny en format GladeXML que els programes poden carregar fàcilment per instanciar-ne els elements gràfics i connectar-hi els gestors d'esdeveniments.
El programari lablgtk2[70] conté els connectors a les API de GTK i Glade així com un convertidor que del format GladeXML genera un mòdul OCaml amb la definició de la interfície. Per exemple, dissenyant al Glade una finestra de nom window1 amb l'etiqueta "Hola món" desant el fitxer com a hola_mon_interficie.glade.
A la versió més recent de Glade, cal especificar a les preferències, com a format de projecte, el libGlade (GladeXML v.2.0)[71] en comptes del GtkBuilder que ve predeterminat.
(* fitxer hola_mon.ml *)openHola_mon_interficieleten_sortir()=GMain.quit();;let()=letw1=newwindow1()in(* crea instància de la finestra importada de "Hola_mon_interficie" *)let_=w1#window1#connect#destroy~callback:en_sortirin(* connecta el senyal de tancament ''on destroy'' *)w1#toplevel#show();(* mostra la finestra *)GMain.Main.main();;(* engega el bucle de despatx de senyals *)
L'antic convertidor ml-glade[72] que traduïa fitxers GladeXML versió 1, no ha sigut actualitzat a les versions de Glade actuals i ha quedat obsolet.
Composició de signatures
A partir de la versió 3.12 permet la inclusió múltiple de signatures mitjançant la substitució de tipus de la segona en endavant.
Vegeu ref.[41]
moduletypePUNT_ABSTRACTE=sigtypetvalgetX:t->floatvalsetX:float->t->tvalmodul:t->floatvaldist:t->t->floatend;;moduletypePUNT1D=sigincludePUNT_ABSTRACTEvalnou:float->tend;;moduletypePROP_Y=sigtypeuvalgetY:u->floatvalsetY:float->u->uend;;moduletypePUNT2D=sigincludePUNT_ABSTRACTEincludePROP_Ywithtypeu:=t(* inclusió de signatures amb substitució de tipus v3.12 *)valnou:float->float->tvaldesplassaX:float->t->tend;;modulePunt1D:PUNT1D=structtypet={x:float}letnoux={x}letgetX{x}=x(* v3.12: encaixos {x} equivalents a {x=x} *)letsetXnova_xp={pwithx=nova_x}letmodulp=p.xletdistpq=modul{x=q.x-.p.x}end;;modulePunt2D:PUNT2D=structtypet={x:float;y:float}letnouxy={x;y}letgetX{x}=xletsetXnova_xp={pwithx=nova_x}letgetY{y}=yletsetYnova_yp={pwithy=nova_y}letmodulp=sqrt(p.x**2.0+.p.y**2.0)letdistpq=modul{x=q.x-.p.x;y=q.y-.p.y}letdesplassaXdx({x}asp)={pwithx=x+.dx}(* ''as pattern'' al revés del Haskell *)end;;
Productor / consumidor
Més info al manual "Unix systems programming in Objective Caml"[73]
openEventtypeinfo=Infoofint|PlegaexceptionEFinalofstringletobtenir_hora:unit->string=(* obtenir hora d'Unix.time *)fun()->letopenUnixin(* ''open'' local, versió 3.12 *)lettm=localtime(time())inPrintf.sprintf"%02d:%02d:%02d"tm.tm_hourtm.tm_mintm.tm_secletproductor:infochannel->unit=funcanal->forvalor=3downto0doThread.delay1.0;sync(sendcanal(Infovalor))(* tramesa síncrona *)doneletconsumidor:infochannel*infochannel->unit=fun(canal1,canal2)->try(whiletruedomatchsync(choose[receivecanal1;receivecanal2])(* recepció síncrona amb selecció *)with|Infovalor->Printf.printf"%s - compte: %d \n"(obtenir_hora())valor;flushstdout|Plega->raise(EFinal"s'ha acabat")(* excepció per sortir del bucle *)done)with|EFinalmsg->Printf.printf"excep %s\n"msg;flushstdout;;letmain()=letcanal1=Event.new_channel()inletcanal2=Event.new_channel()inletconsumidor_id=Thread.createconsumidor(canal1,canal2)inletproductor_id=Thread.createproductorcanal1in(* pel canal1 *)Thread.joinproductor_id(* espera fi productor *);sync(sendcanal2Plega)(* ordre de plegar al consumidor *)(* pel canal2 *);Thread.joinconsumidor_id(* espera fi consumidor *);print_string"Fi del programa\n";;let_=main()
Compilació amb fils d'exec. lleugers (del planificador de l'intèrpret) (Lightweight threads)[74] (només a bytecode): ocamlc -vmthread
(* fitxer mvar.ml, adaptat dels exemples de cothreads *)moduleThread=CothreadopenStmtype'amvar='aoptiontvar(* -------- primitives similars a les del Haskell -------- *)letnew_empty_mvar()=tvarNonelettake_mvarmv=read_tvarmv>>=(functionNone->retry|Somev->write_tvarmvNone>>=(function_->returnv))letput_mvarmvv=read_tvarmv>>=(functionNone->write_tvarmv(Somev)|Somev->retry)(* -------- aplicació -------- *)letproductormv=letc=ref0inwhiletruedoThread.delay(Random.float0.00005);atom(put_mvarmv!c);incrcdoneletconsumidormv=whiletruedoPrintf.printf"Rebut %d\n"(atom(take_mvarmv));flush_all();doneletmain()=letmv=new_empty_mvar()inletprod_id=Thread.createproductormvinletconsum_id=Thread.createconsumidormvinThread.joinprod_id;Thread.joinconsum_id;()let()=main()
Compilació amb fils d'exec. lleugers (del planificador de l'intèrpret), a codi intermedi ocaml.