OCaml ( / oʊ ˈ k æ m əl / oh- KAM -əl , anteriormente Objective Caml ) es un lenguaje de programación multiparadigma de alto nivel y propósito general que extiende el dialecto Caml de ML con características orientadas a objetos . OCaml fue creado en 1996 por Xavier Leroy , Jérôme Vouillon, [5] Damien Doligez , Didier Rémy, [6] Ascánder Suárez y otros.
La cadena de herramientas OCaml incluye un intérprete interactivo de nivel superior , un compilador de bytecode , un compilador de código nativo optimizador , un depurador reversible y un administrador de paquetes (OPAM). OCaml se desarrolló inicialmente en el contexto de la demostración automatizada de teoremas y se utiliza en análisis estático y software de métodos formales . Más allá de estas áreas, se ha utilizado en programación de sistemas , desarrollo web y utilidades financieras específicas, entre otros dominios de aplicación.
El acrónimo CAML originalmente significaba Categorical Abstract Machine Language (Lenguaje de máquina abstracta categórica ), pero OCaml omite esta máquina abstracta . [7] OCaml es un proyecto de software libre y de código abierto administrado y mantenido principalmente por el Instituto Francés de Investigación en Ciencias de la Computación y Automatización (Inria). A principios de la década de 2000, muchos lenguajes adoptaron elementos de OCaml, en particular F# y Scala .
Los lenguajes derivados de ML son más conocidos por sus sistemas de tipos estáticos y compiladores de inferencia de tipos . OCaml unifica la programación funcional , imperativa y orientada a objetos bajo un sistema de tipos similar a ML. Por lo tanto, los programadores no necesitan estar muy familiarizados con el paradigma del lenguaje funcional puro para usar OCaml.
Al exigir al programador que trabaje dentro de las limitaciones de su sistema de tipos estáticos , OCaml elimina muchos de los problemas de ejecución relacionados con los tipos asociados con los lenguajes de tipado dinámico . Además, el compilador de inferencia de tipos de OCaml reduce en gran medida la necesidad de las anotaciones de tipos manuales que se requieren en la mayoría de los lenguajes de tipado estático. Por ejemplo, los tipos de datos de las variables y las firmas de las funciones normalmente no necesitan declararse explícitamente, como ocurre en lenguajes como Java y C# , porque se pueden inferir a partir de los operadores y otras funciones que se aplican a las variables y otros valores en el código. El uso eficaz del sistema de tipos de OCaml puede requerir cierta sofisticación por parte de un programador, pero esta disciplina se ve recompensada con un software confiable y de alto rendimiento.
OCaml se distingue quizás más de otros lenguajes con orígenes académicos por su énfasis en el rendimiento. Su sistema de tipos estáticos evita las discrepancias de tipos en tiempo de ejecución y, por lo tanto, evita las comprobaciones de seguridad y de tipos en tiempo de ejecución que afectan al rendimiento de los lenguajes tipados dinámicamente, al tiempo que garantiza la seguridad en tiempo de ejecución, excepto cuando la comprobación de límites de matriz está desactivada o cuando se utilizan algunas características de tipo no seguro como la serialización . Estos son lo suficientemente raros como para que evitarlos sea bastante posible en la práctica.
Aparte de la sobrecarga de verificación de tipos, los lenguajes de programación funcional son, en general, difíciles de compilar para obtener un código de lenguaje de máquina eficiente, debido a problemas como el problema funarg . Junto con las optimizaciones estándar de bucles, registros e instrucciones, el compilador optimizador de OCaml emplea métodos de análisis de programas estáticos para optimizar el encasillamiento de valores y la asignación de cierres , lo que ayuda a maximizar el rendimiento del código resultante incluso si hace un uso extensivo de construcciones de programación funcional.
Xavier Leroy ha afirmado que "OCaml ofrece al menos el 50% del rendimiento de un compilador de C decente", [8] aunque una comparación directa es imposible. Algunas funciones en la biblioteca estándar de OCaml se implementan con algoritmos más rápidos que las funciones equivalentes en las bibliotecas estándar de otros lenguajes. Por ejemplo, la implementación de la unión de conjuntos en la biblioteca estándar de OCaml en teoría es asintóticamente más rápida que la función equivalente en las bibliotecas estándar de lenguajes imperativos (por ejemplo, C++, Java) porque la implementación de OCaml puede explotar la inmutabilidad de los conjuntos para reutilizar partes de los conjuntos de entrada en la salida (ver estructura de datos persistentes ).
Entre los años 1970 y 1980, Robin Milner , un científico informático británico y ganador del premio Turing , trabajó en el Laboratorio de Fundamentos de la Informática de la Universidad de Edimburgo . [9] [10] Milner y otros estaban trabajando en demostradores de teoremas , que históricamente se desarrollaron en lenguajes como Lisp . Milner se topó repetidamente con el problema de que los demostradores de teoremas intentarían afirmar que una prueba era válida juntando pruebas no válidas. [10] Como resultado, continuó desarrollando el metalenguaje para su Logic for Computable Functions , un lenguaje que solo permitiría al escritor construir pruebas válidas con su sistema de tipos polimórficos. [11] ML se convirtió en un compilador para simplificar el uso de LCF en diferentes máquinas y, en la década de 1980, se convirtió en un sistema completo propio. [11] ML eventualmente serviría como base para la creación de OCaml.
A principios de la década de 1980, hubo algunos desarrollos que impulsaron al equipo Formel de INRIA a interesarse en el lenguaje ML. Luca Cardelli , profesor de investigación de la Universidad de Oxford , utilizó su máquina abstracta funcional para desarrollar una implementación más rápida de ML, y Robin Milner propuso una nueva definición de ML para evitar la divergencia entre varias implementaciones. Simultáneamente, Pierre-Louis Curien, investigador sénior de la Universidad Paris Diderot , desarrolló un cálculo de combinadores categóricos y lo vinculó al cálculo lambda , lo que condujo a la definición de la máquina abstracta categórica (CAM). Guy Cousineau, investigador de la Universidad Paris Diderot, reconoció que esto podría aplicarse como un método de compilación para ML. [12]
Caml fue diseñado y desarrollado inicialmente por el equipo Formel de INRIA, encabezado por Gérard Huet . La primera implementación de Caml se creó en 1987 y se siguió desarrollando hasta 1992. Aunque estuvo encabezado por Ascánder Suárez, Pierre Weis y Michel Mauny continuaron con el desarrollo después de que él se fuera en 1988. [12]
Guy Cousineau recuerda que su experiencia en la implementación de lenguajes de programación fue muy limitada al principio y que había múltiples deficiencias de las que era responsable. A pesar de esto, cree que "Ascander, Pierre y Michel hicieron un buen trabajo". [12]
Entre 1990 y 1991, Xavier Leroy diseñó una nueva implementación de Caml basada en un intérprete de bytecode escrito en C. Además de esto, Damien Doligez escribió un sistema de gestión de memoria, también conocido como recolector de basura secuencial , para esta implementación. [11] Esta nueva implementación, conocida como Caml Light , reemplazó a la antigua implementación de Caml y se ejecutó en pequeñas máquinas de escritorio. [12] En los años siguientes, aparecieron bibliotecas como las herramientas de manipulación de sintaxis de Michel Mauny y ayudaron a promover el uso de Caml en equipos educativos y de investigación. [11]
En 1995, Xavier Leroy lanzó Caml Special Light, que era una versión mejorada de Caml. [12] Se agregó un compilador de código nativo optimizador al compilador de bytecode, lo que aumentó en gran medida el rendimiento a niveles comparables con lenguajes convencionales como C++ . [11] [12] Además, Leroy diseñó un sistema de módulos de alto nivel inspirado en el sistema de módulos de Standard ML que proporcionaba potentes facilidades para la abstracción y la parametrización y facilitaba la construcción de programas a gran escala. [11]
Didier Rémy y Jérôme Vouillon diseñaron un sistema de tipos expresivos para objetos y clases, que se integró en Caml Special Light. Esto condujo al surgimiento del lenguaje Objective Caml, lanzado por primera vez en 1996 y posteriormente renombrado como OCaml en 2011. Este sistema de objetos admitía notablemente muchos modismos orientados a objetos predominantes de una manera estáticamente segura para los tipos, mientras que esos mismos modismos causaban problemas o requerían verificaciones en tiempo de ejecución en lenguajes como C++ o Java . En 2000, Jacques Garrigue amplió Objective Caml con múltiples características nuevas, como métodos polimórficos, variantes y argumentos etiquetados y opcionales. [11] [12]
Se han agregado mejoras al lenguaje de manera incremental durante las últimas dos décadas para respaldar las crecientes bases de código comerciales y académicas en OCaml. [11] La versión OCaml 4.0 en 2012 agregó tipos de datos algebraicos generalizados (GADT) y módulos de primera clase para aumentar la flexibilidad del lenguaje. [11] La versión OCaml 5.0.0 en 2022 [13] es una reescritura completa del entorno de ejecución del lenguaje, que elimina el bloqueo GC global y agrega controladores de efectos a través de continuaciones delimitadas . Estos cambios permiten la compatibilidad con el paralelismo de memoria compartida y la concurrencia daltónica, respectivamente.
El desarrollo de OCaml continuó dentro del equipo Cristal en INRIA hasta 2005, cuando fue sucedido por el equipo Gallium. [14] Posteriormente, Gallium fue sucedido por el equipo Cambium en 2019. [15] [16] A partir de 2023, hay 23 desarrolladores principales de la distribución del compilador de una variedad de organizaciones [17] y 41 desarrolladores para el ecosistema más amplio de herramientas y empaquetado de OCaml. [18]
OCaml presenta un sistema de tipos estáticos , inferencia de tipos , polimorfismo paramétrico , recursión de cola , coincidencia de patrones , cierres léxicos de primera clase , functores (módulos paramétricos) , manejo de excepciones , manejo de efectos y recolección de basura automática generacional incremental .
OCaml se destaca por extender la inferencia de tipos de estilo ML a un sistema de objetos en un lenguaje de propósito general. Esto permite la subtipificación estructural , donde los tipos de objetos son compatibles si sus firmas de método son compatibles, independientemente de su herencia declarada (una característica inusual en lenguajes tipados estáticamente).
Se proporciona una interfaz de funciones externas para vincular a primitivas de C , incluido soporte de lenguaje para matrices numéricas eficientes en formatos compatibles con C y Fortran . OCaml también admite la creación de bibliotecas de funciones de OCaml que se pueden vincular a un programa principal en C, de modo que se pueda distribuir una biblioteca de OCaml a programadores de C que no tengan conocimiento o instalación de OCaml.
La distribución de OCaml contiene:
El compilador de código nativo está disponible para muchas plataformas, incluidas Unix , Microsoft Windows y Apple macOS . La portabilidad se logra mediante la compatibilidad con la generación de código nativo para las principales arquitecturas:
El compilador de bytecode admite el funcionamiento en cualquier arquitectura de 32 o 64 bits cuando la generación de código nativo no está disponible y solo requiere un compilador de C.
Los programas de código nativo y bytecode de OCaml se pueden escribir en un estilo multiproceso , con cambio de contexto preventivo. Los subprocesos de OCaml en el mismo dominio [20] se ejecutan solo mediante tiempo compartido. Sin embargo, un programa de OCaml puede contener varios dominios.
Los fragmentos de código OCaml se estudian más fácilmente si se introducen en el REPL de nivel superior . Se trata de una sesión OCaml interactiva que imprime los tipos inferidos de las expresiones resultantes o definidas. [21] El nivel superior de OCaml se inicia simplemente ejecutando el programa OCaml:
$ ocaml Objetivo Caml versión 3.09.0 #
Luego se puede ingresar el código en el mensaje "#". Por ejemplo, para calcular 1+2*3:
# 1 + 2 * 3 ;; - : int = 7
OCaml infiere que el tipo de expresión es "int" (un entero con precisión de máquina ) y da el resultado "7".
El siguiente programa "hello.ml":
print_endline "¡Hola mundo!"
se puede compilar en un ejecutable de bytecode:
$ ocamlc hola.ml -o hola
o compilado en un ejecutable de código nativo optimizado:
$ ocamlopt hola.ml -o hola
y ejecutado:
$ ./hello ¡Hola mundo! $
El primer argumento de ocamlc, "hello.ml", especifica el archivo fuente a compilar y el indicador "-o hello" especifica el archivo de salida. [22]
El option
constructor de tipos en OCaml, similar al Maybe
tipo en Haskell , aumenta un tipo de datos dado para devolver Some
un valor del tipo de datos dado o para devolver None
. [23] Esto se utiliza para expresar que un valor podría o no estar presente.
# Algunos 42 ;; - : int opción = Algunos 42 # Ninguno ;; - : ' una opción = Ninguno
Este es un ejemplo de una función que extrae un int de una opción, si hay uno adentro, y lo convierte en una cadena , o si no, devuelve una cadena vacía:
deje que extraiga o = coincida con o con | Algún i -> cadena_de_int i | Ninguno -> "" ;;
#extraer ( Algunos 42 );; - : cadena = " 42 " #extraerNinguno ;; - : cadena = " "
Las listas son uno de los tipos de datos fundamentales en OCaml. El siguiente ejemplo de código define una función recursiva sum que acepta un argumento, entires , que se supone que es una lista de enteros. Tenga en cuenta la palabra clave rec
que indica que la función es recursiva. La función itera recursivamente sobre la lista de enteros dada y proporciona una suma de los elementos. La declaración match tiene similitudes con el elemento switch de C , aunque es mucho más general.
deje que rec sume enteros = (* La palabra clave rec significa 'recursivo'. *) coincide con enteros con | [] -> 0 (* Resultado 0 si enteros es la lista vacía []. *) | primero :: resto -> primero + suma resto ;; (* Llamada recursiva si enteros es una lista no vacía; primero es el primer elemento de la lista y resto es una lista del resto de los elementos, posiblemente []. *)
# suma [ 1 ; 2 ; 3 ; 4 ; 5 ];; - : int = 15
Otra forma es utilizar la función de plegado estándar que funciona con listas.
deje que suma enteros = List . fold_left ( fun acumulador x -> acumulador + x ) 0 enteros ;;
# suma [ 1 ; 2 ; 3 ; 4 ; 5 ];; - : int = 15
Dado que la función anónima es simplemente la aplicación del operador +, esto se puede abreviar a:
sea suma de enteros = List . fold_left (+) 0 enteros
Además, se puede omitir el argumento de lista haciendo uso de una aplicación parcial :
sea suma = Lista . fold_left (+) 0
OCaml se presta a expresar algoritmos recursivos de forma concisa. El siguiente ejemplo de código implementa un algoritmo similar a quicksort que ordena una lista en orden creciente.
deje que rec qsort = función | [] -> [] | pivote :: resto -> deje que sea_menos x = x < pivote en deje que izquierda , derecha = Lista . partición sea_menos resto en qsort izquierda @ [ pivote ] @ qsort derecha
O utilizando la aplicación parcial del operador >=.
deje que rec qsort = función | [] -> [] | pivote :: resto -> deje que sea_menos = (>=) pivote en deje que sea_menos izquierda , derecha = Lista . partición sea_menos resto en qsort izquierda @ [ pivote ] @ qsort derecha
El siguiente programa calcula el menor número de personas en una sala para quienes la probabilidad de cumpleaños completamente únicos es menor al 50% (el problema del cumpleaños , donde para 1 persona la probabilidad es 365/365 (o 100%), para 2 es 364/365, para 3 es 364/365 × 363/365, etc.) (respuesta = 23).
sea year_size = 365 .deje que rec birthday_paradox prob personas = deje que prob = ( year_size -. float personas ) / . year_size *. prob in si prob < 0. 5 entonces Printf . printf "respuesta = %d \n " ( personas + 1 ) de lo contrario birthday_paradox prob ( personas + 1 ) ;; paradoja_del_cumpleaños 1 . 0 1
El código siguiente define una codificación Church de números naturales , con sucesor (succ) y adición (add). Un numeral Church n
es una función de orden superior que acepta una función f
y un valor x
y se aplica f
a tiempos x
exactos n
. Para convertir un numeral Church de un valor funcional a una cadena, le pasamos una función que antepone la cadena "S"
a su entrada y la constante string "0"
.
sea cero f x = x sea succ n f x = f ( n f x ) sea uno = succ cero sea dos = succ ( succ cero ) sea sumar n1 n2 f x = n1 f ( n2 f x ) sea to_string n = n ( fun k -> "S" ^ k ) "0" sea _ = to_string ( add ( succ dos ) dos )
Se puede acceder directamente a una variedad de bibliotecas desde OCaml. Por ejemplo, OCaml tiene una biblioteca incorporada para aritmética de precisión arbitraria . Como la función factorial crece muy rápidamente, rápidamente desborda los números de precisión de máquina (normalmente de 32 o 64 bits). Por lo tanto, factorial es un candidato adecuado para la aritmética de precisión arbitraria.
En OCaml, el módulo Num (ahora reemplazado por el módulo ZArith) proporciona aritmética de precisión arbitraria y se puede cargar en un nivel superior en ejecución usando:
# # usa "topfind" ;; # # requiere "num" ;; # abre Num ;;
La función factorial puede entonces escribirse utilizando los operadores numéricos de precisión arbitraria =/ , */ y -/ :
# deje que rec fact n = si n =/ Int 0 entonces Int 1 de lo contrario n */ fact ( n -/ Int 1 );; val fact : Num . num -> Num . num = < fun >
Esta función puede calcular factoriales mucho más grandes, como 120!:
# cadena_de_num ( hecho ( Int 120 ));; - : cadena = "6689502913449127057588118054090372586752746333138029810295671352301633 55724496298936687416527198498130815763789321409055253440858940812185989 8481114389650005964960521256960000000000000000000000000000000"
El siguiente programa renderiza un triángulo giratorio en 2D usando OpenGL :
dejar () = ignorar ( Glut . init Sys . argv ); Glut . initDisplayMode ~ double_buffer : true () ; ignorar ( Glut . createWindow ~ título : "Demostración de OpenGL" ); dejar ángulo t = 10 . * . t * . t en dejar render () = GlClear . clear [ ` color ]; GlMat . load_identity () ; GlMat . rotate ~ ángulo : ( ángulo ( Sys . time () )) ~ z : 1 . () ; GlDraw . starts ` triángulos ; Lista . iter GlDraw . vertex2 [- 1 ., - 1 .; 0 ., 1 .; 1 ., - 1 .]; GlDraw . ends () ; Glut . swapBuffers ( ) en GlMat.modo ` modelview ; Glut.displayFunc ~ cb : render ; Glut.idleFunc ~ cb : ( Algunos Glut.postRedisplay ) ; Glut.mainLoop ( )
Se requieren los enlaces de LabGL a OpenGL. Luego, el programa se puede compilar en código de bytes con:
$ ocamlc -I +lablGL lablglut.cma lablgl.cma simple.ml -o simple
o a nativecode con:
$ ocamlopt -I +lablGL lablglut.cmxa lablgl.cmxa simple.ml -o simple
o, más simplemente, usando el comando de compilación ocamlfind
$ ocamlfind opt simple.ml -paquete lablgl.glut -linkpkg -o simple
y corre:
$ ./simple
En OCaml se pueden desarrollar programas gráficos 2D y 3D mucho más sofisticados y de alto rendimiento. Gracias al uso de OpenGL y OCaml, los programas resultantes pueden ser multiplataforma y compilarse sin cambios en muchas plataformas importantes.
El código siguiente calcula la secuencia de Fibonacci de un número n ingresado. Utiliza recursión de cola y coincidencia de patrones.
sea fib n = sea rec fib_aux m a b = coincida m con | 0 -> a | _ -> fib_aux ( m - 1 ) b ( a + b ) en fib_aux n 0 1
Las funciones pueden tomar funciones como entrada y devolver funciones como resultado. Por ejemplo, si se aplica f dos veces a una función , se obtiene una función que aplica f dos veces a su argumento.
sea dos veces ( f : ' a -> ' a ) = fun ( x : ' a ) -> f ( f x );; sea inc ( x : int ) : int = x + 1 ;; sea add2 = dos veces inc ;; sea inc_str ( x : cadena ) : cadena = x ^ " " ^ x ;; sea add_str = dos veces ( inc_str );;
# add2 98 ;; - : int = 100 # add_str "Prueba" ;; - : string = "Prueba Prueba Prueba Prueba"
La función twice utiliza una variable de tipo 'a para indicar que se puede aplicar a cualquier función f que mapee un tipo ' a a sí misma, en lugar de solo a funciones int->int . En particular, twice se puede aplicar incluso a sí misma.
# sea cuatro veces f = ( dos veces dos veces ) f ;; val cuatro veces : ( ' a -> ' a ) - > ' a - > ' a = <fun> # sea sumar4 = cuatro veces inc ;; val sumar4 : int - > int = <fun> # sumar4 98 ;; - : int = 102
MetaOCaml [24] es una extensión de programación de múltiples etapas de OCaml que permite la compilación incremental de nuevo código de máquina durante el tiempo de ejecución. En algunas circunstancias, es posible lograr importantes aumentos de velocidad mediante la programación de múltiples etapas, ya que en el tiempo de ejecución hay disponible información más detallada sobre los datos a procesar que en el tiempo de compilación normal, por lo que el compilador incremental puede optimizar muchos casos de verificación de condiciones, etc.
A modo de ejemplo: si en tiempo de compilación se sabe que a menudo se necesita alguna función de potencia , pero el valor de se conoce solo en tiempo de ejecución, se puede utilizar una función de potencia de dos etapas en MetaOCaml:x -> x^n
n
sea rec potencia n x = si n = 0 entonces .< 1 >. de lo contrario si es par n entonces sqr ( potencia ( n / 2 ) x ) de lo contrario .<.~ x *. .~( potencia ( n - 1 ) x )>.
Una vez que n
se conoce el tiempo de ejecución, se puede crear una función de potencia especializada y muy rápida:
.< fun x -> .~( potencia 5 .< x >.)>.
El resultado es:
diversión x_1 -> ( x_1 * sea y_3 = sea y_2 = ( x_1 * 1 ) en ( y_2 * y_2 ) en ( y_3 * y_3 ))
La nueva función se compila automáticamente.
genfft
.Al menos varias docenas de empresas utilizan OCaml en algún grado. [30] Algunos ejemplos notables incluyen: