OCaml ( / oʊ ˈ k æ m əl / oh- KAM -əl , anteriormente Objective Caml ) es un lenguaje de programación multiparadigma , de alto nivel y de 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, Damien Doligez , Didier Rémy, Ascander Suárez y otros.
La cadena de herramientas OCaml incluye un intérprete interactivo de nivel superior , un compilador de código de bytes , un compilador de código nativo de optimización, 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 software de análisis estático y métodos formales . Más allá de estas áreas, ha encontrado uso en programación de sistemas , desarrollo web y utilidades financieras específicas, entre otros dominios de aplicaciones.
El acrónimo CAML originalmente significaba Lenguaje de máquina abstracto categórico , pero OCaml omite esta máquina abstracta . [5] OCaml es un proyecto de software gratuito y de código abierto gestionado y mantenido principalmente por el Instituto Francés de Investigación en Informática 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 mejor 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 utilizar OCaml.
Al requerir que el programador trabaje dentro de las limitaciones de su sistema de tipos estáticos, OCaml elimina muchos de los problemas de tiempo de ejecución relacionados con los tipos asociados con los lenguajes escritos dinámicamente . Además, el compilador de inferencia de tipos de OCaml reduce en gran medida la necesidad de anotaciones de tipo manuales que se requieren en la mayoría de los lenguajes de tipo estático. Por ejemplo, normalmente no es necesario declarar explícitamente los tipos de datos de las variables y las firmas de las funciones, como ocurre en lenguajes como Java y C# , porque pueden inferirse 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 quizás se distinga más de otros lenguajes con orígenes académicos por su énfasis en el rendimiento. Su sistema de tipos estáticos evita discrepancias entre los tipos de tiempo de ejecución y, por lo tanto, evita las comprobaciones de seguridad y de tipos de tiempo de ejecución que sobrecargan el rendimiento de los lenguajes escritos dinámicamente, al mismo tiempo que garantiza la seguridad del tiempo de ejecución, excepto cuando la verificación de límites de matriz está desactivada o cuando se utilizan algunas funciones de tipo no seguro, como la serialización . . Estos son lo suficientemente raros como para evitarlos en la práctica.
Aparte de la sobrecarga de verificación de tipos, los lenguajes de programación funcionales son, en general, difíciles de compilar en 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 de optimización de OCaml emplea métodos de análisis de programas estáticos para optimizar la asignación de valores y cierres , lo que ayuda a maximizar el rendimiento del código resultante incluso si hace un uso extensivo de construcciones de programación funcionales.
Xavier Leroy ha declarado que "OCaml ofrece al menos el 50% del rendimiento de un compilador de C decente", [6] aunque una comparación directa es imposible. Algunas funciones de la biblioteca estándar OCaml se implementan con algoritmos más rápidos que 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 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 OCaml puede explotar la inmutabilidad de los conjuntos para reutilizar partes de conjuntos de entrada en la salida (ver estructura de datos persistentes ).
Entre los años 1970 y 1980, Robin Milner , un informático británico y ganador del Premio Turing , trabajó en el Laboratorio de Fundamentos de la Informática de la Universidad de Edimburgo . [7] [8] 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 reuniendo pruebas que no eran válidas. [8] Como resultado, pasó a desarrollar el metalenguaje para su Lógica para funciones computables , un lenguaje que solo permitiría al escritor construir pruebas válidas con su sistema de tipos polimórficos. [9] 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 en sí mismo. [9] ML eventualmente serviría como base para la creación de OCaml.
A principios de la década de 1980, hubo algunos desarrollos que llevaron al equipo Formel de INRIA a interesarse en el lenguaje ML. Luca Cardelli , profesor de investigación en 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 divergencias entre varias implementaciones. Simultáneamente, Pierre-Louis Curien, investigador principal 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 técnica de compilación para ML. [10]
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 desarrolló hasta 1992. Aunque fue encabezada por Ascander Suárez, Pierre Weis y Michel Mauny continuaron con el desarrollo después de que él se fue en 1988. [10]
Se cita a Guy Cousineau recordando que su experiencia con la implementación de lenguajes de programación fue inicialmente muy limitada y que había múltiples deficiencias de las que él es responsable. A pesar de ello, considera que "Ascander, Pierre y Michel hicieron un trabajo bastante bonito". [10]
Entre 1990 y 1991, Xavier Leroy diseñó una nueva implementación de Caml basada en un intérprete de código de bytes 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. [9] Esta nueva implementación, conocida como Caml Light , reemplazó la antigua implementación Caml y se ejecutó en pequeñas máquinas de escritorio. [10] En los años siguientes, aparecieron bibliotecas como las herramientas de manipulación de sintaxis de Michel Mauny que ayudaron a promover el uso de Caml en equipos educativos y de investigación. [9]
En 1995, Xavier Leroy lanzó Caml Special Light, que era una versión mejorada de Caml. [10] Se agregó un compilador de código nativo optimizado al compilador de código de bytes, lo que aumentó considerablemente el rendimiento a niveles comparables con los lenguajes convencionales como C++ . [9] [10] Además, Leroy diseñó un sistema de módulos de alto nivel inspirado en el sistema de módulos de Standard ML que proporcionó potentes funciones de abstracción y parametrización y facilitó la construcción de programas a mayor escala. [9]
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 llevó al surgimiento del lenguaje Objective Caml, lanzado por primera vez en 1996 y posteriormente renombrado a OCaml en 2011. Este sistema de objetos admitía notablemente muchos modismos predominantes orientados a objetos de una manera estáticamente segura, mientras que esos mismos modismos causaban falta de solidez o eran necesarios. Comprobaciones de 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. [9] [10]
Se han agregado mejoras de lenguaje de manera incremental durante las últimas dos décadas para respaldar las crecientes bases de código comerciales y académicas en OCaml. [9] 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. [9] La versión OCaml 5.0.0 en 2022 [11] es una reescritura completa del tiempo de ejecución del lenguaje, eliminando el bloqueo global de GC y agregando 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 de INRIA hasta 2005, cuando fue sucedido por el equipo Gallium. [12] Posteriormente, Gallium fue reemplazado por el equipo de Cambium en 2019. [13] [14] A partir de 2023, hay 23 desarrolladores principales de la distribución del compilador de una variedad de organizaciones [15] y 41 desarrolladores para las herramientas OCaml más amplias. y ecosistema de embalaje. [dieciséis]
OCaml presenta un sistema de tipos estáticos , inferencia de tipos , polimorfismo paramétrico , recursividad de cola , coincidencia de patrones , cierres léxicos de primera clase , funtores (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étodos son compatibles, independientemente de su herencia declarada (una característica inusual en lenguajes de tipado estático).
Se proporciona una interfaz de función externa para vincular a primitivas de C , incluido soporte de lenguaje para matrices numéricas eficientes en formatos compatibles tanto con C como con Fortran . OCaml también admite la creación de bibliotecas de funciones OCaml que se pueden vincular a un programa principal en C, de modo que se pueda distribuir una biblioteca OCaml a programadores de C que no tengan conocimiento ni instalación de OCaml.
La distribución 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 el soporte de generación de código nativo para las principales arquitecturas:
El compilador de código de bytes 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 de bytes y código nativo OCaml se pueden escribir en un estilo multiproceso , con cambio de contexto preventivo. Los subprocesos OCaml en el mismo dominio [18] se ejecutan únicamente compartiendo tiempo. Sin embargo, un programa OCaml puede contener varios dominios. Existen varias bibliotecas para informática distribuida, como Functory y ocamlnet/Plasma.
Desde 2011, se han aportado muchas herramientas y bibliotecas nuevas al entorno de desarrollo OCaml: [19]
Los fragmentos de código OCaml se estudian más fácilmente ingresándolos en el REPL de nivel superior . Esta es una sesión OCaml interactiva que imprime los tipos inferidos de expresiones resultantes o definidas. [20] 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 ;; - : entero = 7
OCaml infiere que el tipo de expresión es "int" (un entero de precisión máquina ) y da el resultado "7".
El siguiente programa "hola.ml":
print_endline "¡Hola mundo!"
se puede compilar en un ejecutable de código de bytes:
$ ocamlc hola.ml -o hola
o compilado en un ejecutable de código nativo optimizado:
$ ocamlopt hola.ml -o hola
y ejecutado:
$ ./hola ¡Hola mundo! ps
El primer argumento de ocamlc, "hello.ml", especifica el archivo fuente a compilar y el indicador "-o hello" especifica el archivo de salida. [21]
El option
constructor de tipos en OCaml, similar al Maybe
tipo en Haskell , aumenta un tipo de datos dado para devolver Some
el valor del tipo de datos dado o para devolver None
. [22] Esto se utiliza para expresar que un valor puede estar presente o no.
# Unos 42 ;; - : opción int = Algunos 42 # Ninguno ;; - : ' una opción = Ninguna
Este es un ejemplo de una función que extrae un int de una opción, si hay una dentro, y la convierte en una cadena, o si no, devuelve una cadena vacía:
dejar extraer o = hacer coincidir o con | Algunos i -> string_of_int i | Ninguno -> "" ;;
# extraer ( Algunos 42 );; - : cadena = "42" # extraer Ninguno ;; - : 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, enteros , que se supone que es una lista de números enteros. Tenga en cuenta la palabra clave rec
que indica que la función es recursiva. La función itera recursivamente sobre la lista dada de números enteros 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.
let rec sum enteros = (* La palabra clave rec significa 'recursivo'. *) haga coincidir números enteros con | [] -> 0 (* Da 0 si los números enteros son 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 ];; - : entero = 15
Otra forma es utilizar la función de plegado estándar que funciona con listas.
vamos a sumar números enteros = Lista . fold_left ( acumulador divertido x -> acumulador + x ) 0 enteros ;;
# suma [ 1 ; 2 ; 3 ; 4 ; 5 ];; - : entero = 15
Dado que la función anónima es simplemente la aplicación del operador +, esto se puede abreviar a:
vamos a sumar números enteros = Lista . fold_left (+) 0 enteros
Además, se puede omitir el argumento de la lista haciendo uso de una aplicación parcial :
let suma = Lista . doblar_izquierda (+) 0
OCaml se presta a expresar de forma concisa algoritmos recursivos. El siguiente ejemplo de código implementa un algoritmo similar a la ordenación rápida que ordena una lista en orden creciente.
let rec qsort = función | [] -> [] | pivote :: resto -> let is_less x = x < pivote en let izquierda , derecha = Lista . partición is_less resto en qsort izquierda @ [ pivote ] @ qsort derecha
O usando la aplicación parcial del operador >=.
let rec qsort = función | [] -> [] | pivote :: resto -> let is_less = (>=) pivote en let izquierda , derecha = Lista . partición is_less resto en qsort izquierda @ [ pivote ] @ qsort derecha
El siguiente programa calcula el número más pequeño de personas en una habitación para quienes la probabilidad de cumpleaños completamente únicos es inferior al 50% (el problema de 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 tamaño_año = 365 .let rec cumpleaños_paradoja prob personas = let prob = ( año_tamaño -. flotar personas ) /. tamaño_año *. prob en si prob < 0 . 5 luego Imprimirf . printf "respuesta = %d \n " ( personas + 1 ) else problema de paradoja_cumpleaños ( personas + 1 ) ;; cumpleaños_paradoja 1 . 0 1
El siguiente código define una codificación Church de números naturales , con sucesor (succ) y suma (add). Un número de Church n
es una función de orden superior que acepta una función f
y un valor x
y se aplica exactamente f
a los tiempos. Para convertir un número de Church de un valor funcional a una cadena, le pasamos una función que antepone la cadena a su entrada y a la cadena constante .x
n
"S"
"0"
deja cero f x = x deja succ n f x = f ( n f x ) deja uno = succ cero deja dos = succ ( succ cero ) deja sumar n1 n2 f x = n1 f ( n2 f x ) deja to_string n = n ( divertido k -> "S" ^ k ) "0" let _ = to_string ( agregar ( 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 . A medida que la función factorial crece muy rápidamente, rápidamente desborda los números de precisión de la máquina (normalmente 32 o 64 bits). Por tanto, el 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:
# # utilizar "topfind" ;; # # requiere "núm" ;; # abrir numero ;;
Luego, la función factorial se puede escribir usando los operadores numéricos de precisión arbitraria =/ , */ y -/ :
# let rec fact n = if n =/ Int 0 entonces Int 1 else n */ fact ( n -/ Int 1 );; hecho val : Núm . número -> Núm . número = < diversión >
Esta función puede calcular factoriales mucho más grandes, como 120!:
# string_of_num ( hecho ( Int 120 ));; - : cadena = "6689502913449127057588118054090372586752746333138029810295671352301633 557244962989366874165271984981308157637893214090 55253440858940812185989 84811143896500059649605212569600000000000000000000000000000"
El siguiente programa representa un triángulo giratorio en 2D usando OpenGL :
let () = ignorar ( Glut . init Sys . argv ); Exceso . initDisplayMode ~ double_buffer : verdadero () ; ignorar ( Glut . createWindow ~ título : "Demostración de OpenGL" ); sea el ángulo t = 10 . *. t *. t en let render () = GlClear . claro [ ` color ]; GlMat . cargar_identidad () ; GlMat . rotar ~ ángulo : ( ángulo ( Sys . tiempo () )) ~ z : 1 . () ; GlDibujar . comienza ` triángulos ; Lista . iter GlDraw . vértice2 [- 1 ., - 1 .; 0. , 1 .; 1. , - 1. ]; GlDibujar . termina () ; Exceso . swapBuffers () en GlMat . modo ` vista de modelo ; Exceso . displayFunc ~ cb : renderizar ; Exceso . idleFunc ~ cb :( Algo de exceso . postRedisplay ); Exceso . bucle principal ()
Se requieren los enlaces de LablGL 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 al código nativo 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 -package lablgl.glut -linkpkg -o simple
y correr:
$ ./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 siguiente código calcula la secuencia de Fibonacci de un número n ingresado. Utiliza recursividad de cola y coincidencia de patrones.
let fib n = let rec fib_aux m a b = emparejar m con | 0 -> un | _ -> 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, aplicar dos veces a una función f produce una función que aplica f dos veces a su argumento.
let dos veces ( f : ' a -> ' a ) = divertido ( x : ' a ) -> f ( f x );; sea inc ( x : int ) : int = x + 1 ;; sea sumar2 = dos veces inc ;; let inc_str ( x : cadena ) : cadena = x ^ " " ^ x ;; let add_str = dos veces ( inc_str );;
# agregar2 98 ;; - : int = 100 # add_str "Prueba" ;; - : cadena = "Prueba Prueba Prueba Prueba"
La función usa dos veces una variable de tipo 'a para indicar que se puede aplicar a cualquier función f mapeando desde un tipo 'a a sí mismo, en lugar de solo a funciones int->int . En particular, dos veces incluso se pueden aplicar a sí mismo.
# let fourtimes f = ( dos veces dos ) f ;; val cuatro veces : ( ' a -> ' a ) -> ' a -> ' a = < diversión > # let add4 = cuatro veces inc ;; val add4 : int -> int = < diversión > # add4 98 ;; - : entero = 102
MetaOCaml [23] 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 aceleraciones significativas utilizando la programación de varias etapas, porque 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.
Como ejemplo: si en tiempo de compilación se sabe que se necesita alguna función de potencia con frecuencia, pero el valor de solo se conoce en tiempo de ejecución, se puede usar 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, entonces sqr ( potencia ( n / 2 ) x ) más . <.~ x *. .~( potencia ( n - 1 ) x )>.
Tan pronto como n
se conozca en tiempo de ejecución, se puede crear una función de energía especializada y muy rápida:
.< diversión x -> .~( potencia 5 .< x >.)>.
El resultado es:
divertido x_1 -> ( x_1 * let y_3 = let 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 hasta cierto punto. [30] Ejemplos notables incluyen: