OCaml

OCaml
Logo.
Fecha de la primera versión 1987 (CAML), 1996 (OCaml)
Paradigma Multiparadigma  : imperativo , funcional , orientado a objetos
Desarrollador Inria
Última versión 4.12.0 (24 de febrero de 2021)
Mecanografía Fuerte , estático
Dialectos JoCaml, Fresh OCaml, GCaml, MetaOCaml, OCamlDuce, OcamlP3L
Influenciado por ML
Influenciado F # , óxido , OPA , Scala
Escrito en OCaml
Sistema de explotación Multiplataforma
Licencia LGPL 2.1
Sitio web ocaml.org
Extensión de archivo ml y mli

OCaml , antes conocido como Objective Caml , es la implementación más avanzada del lenguaje de programación Caml , creado por Xavier Leroy , Jérôme Vouillon , Damien Dolurez , Didier Rémy y sus colaboradores en 1996 . Este lenguaje, de la familia de lenguajes ML , es un proyecto de código abierto dirigido y mantenido principalmente por Inria .

OCaml es el sucesor de Caml Light , al que ha agregado, entre otras cosas, una capa de programación de objetos. El acrónimo CAML proviene de Categorical Abstract Machine Language , un modelo de máquina abstracto que, sin embargo, ya no se usa en versiones recientes de OCaml.

Portátil y potente, OCaml se utiliza en proyectos tan diversos como el software de sincronización de archivos Unison , el asistente de prueba formal Coq o la versión web de Facebook Messenger . Las facilidades de procesamiento simbólico del lenguaje permiten el desarrollo de herramientas de verificación estática, como el proyecto SLAM para pilotos de Windows escrito por Microsoft , o ASTRÉE para ciertos sistemas a bordo de Airbus A380s .

Principios

Caml es un lenguaje funcional aumentado con características que permiten una programación imperativa . OCaml amplía las posibilidades del lenguaje al permitir la programación orientada a objetos y la programación modular . Por todas estas razones, OCaml entra en la categoría de lenguajes multi-paradigma .

Integra estos diferentes conceptos en un sistema de tipos heredado del ML, caracterizado por una tipificación estática , fuerte e inferida .

El sistema de tipos permite una fácil manipulación de estructuras de datos complejas: podemos representar fácilmente tipos algebraicos , es decir tipos jerárquicos y potencialmente recursivos (listas, árboles, etc.), y manipularlos fácilmente utilizando la coincidencia de patrones . Esto convierte a OCaml en un lenguaje de elección en áreas que requieren la manipulación de estructuras de datos complejas, como los compiladores .

La escritura fuerte , así como la ausencia de manipulación explícita de la memoria (presencia de un recolector de basura ) hacen de OCaml un lenguaje muy seguro. También es conocido por su rendimiento, gracias a la presencia de un compilador de código nativo .

Historia

El lenguaje Caml nace del encuentro del lenguaje de programación ML, que ha centrado el equipo Formel de INRIA desde principios de los 80 , y la máquina abstracta categórica CAM Guy Cousineau , basada en el trabajo de Pierre-Louis Curien en 1984 . La primera implementación, escrita por Ascander Suarez (en ese momento estudiante de doctorado en la Universidad Paris Diderot ) y luego mantenida por Pierre Weis y Michel Mauny, se publicó en 1987 . El lenguaje se fue diferenciando gradualmente de su padre ML porque el equipo de Inria quería adaptar un lenguaje a sus propias necesidades y continuar haciéndolo evolucionar, lo que entró en conflicto con la "estabilidad" impuesta por ML a través de los esfuerzos de estandarización de Standard ML.

Las limitaciones de CAM llevaron a la creación de una nueva implementación, desarrollada por Xavier Leroy en 1990 , bajo el nombre de Caml Light . Esta implementación, incluida una versión reciente, todavía se usa en la educación hoy , Aunque el sistema ya no es mantenido por el INRIA , opera a través de un código de bytes de intérprete ( bytecode ) codificado en C , lo que le otorga una gran portabilidad. El sistema de gestión de memoria, diseñado por Damien Dolurez, también apareció en Caml Light. En 1995 , Xavier Leroy publicó una versión de Caml llamada Caml Special Light , que introdujo un compilador de código nativo y un sistema de módulos inspirado en módulos de ML estándar.

OCaml, publicado por primera vez en 1996 , trae a Caml un sistema de objetos diseñado por Didier Rémy y Jérôme Vouillon. Algunas características avanzadas, como variantes polimórficas o etiquetas (que permiten distinguir los argumentos dados a una función por su nombre, en lugar de su posición) fueron introducidas en 2000 por Jacques Garrigue. OCaml se ha estabilizado relativamente desde entonces (a pesar de la ausencia de una especificación, el documento vigente es el manual oficial mantenido por Inria ). Han aparecido muchos dialectos de OCaml y continúan explorando aspectos específicos de los lenguajes de programación (concurrencia, paralelismo, evaluación perezosa, integración XML…); consulte la sección Idiomas derivados .

Características principales

Idioma funcional

OCaml tiene la mayoría de las características comunes de los lenguajes funcionales, particularmente funciones de orden superior y cierres ( cierres ), y un buen soporte de la recursividad de cola .

Mecanografía

La escritura estática de OCaml detecta una gran cantidad de errores de programación en tiempo de compilación que podrían causar problemas en tiempo de ejecución. Sin embargo, a diferencia de la mayoría de los otros lenguajes, no es necesario especificar el tipo de variables que está utilizando. De hecho, Caml tiene un algoritmo de inferencia de tipos que le permite determinar el tipo de variables a partir del contexto en el que se utilizan.

El sistema de tipificación ML admite polimorfismo paramétrico, es decir, tipos cuyas partes estarán indeterminadas cuando se defina el valor. Esta característica, automática, permite un genérico comparable a los genéricos en Java o C # o plantillas en C ++ .

Sin embargo, las extensiones de mecanografía ML requeridas por la integración de funcionalidades avanzadas, como la programación orientada a objetos, en ciertos casos hacen que el sistema de tipos sea más complejo: el uso de estas funcionalidades puede requerir un tiempo de aprendizaje para el programador, que no requiere no está necesariamente familiarizado con sistemas de tipos sofisticados.

Filtración

La coincidencia de patrones (en inglés  : coincidencia de patrones ) es un elemento esencial del lenguaje Caml. Permite aligerar el código gracias a una escritura más flexible que las condiciones tradicionales, y la exhaustividad es objeto de un control: el compilador propone un contraejemplo cuando se detecta un filtrado incompleto. Por ejemplo, el siguiente código está compilado pero genera una advertencia:

# type etat = Actif | Inactif | Inconnu;; type etat = Actif | Inactif | Inconnu # let est_actif = function # | Actif -> true # | Inactif -> false;; val est_actif : etat -> bool = <fun> Warning P: this pattern-matching is not exhaustive. Here is an example of a value that is not matched: Inconnu

Por lo tanto, el programa funciona cuando se llama a la función is_active con un estado de Activo o Inactivo , pero si es Desconocido , la función lanza la excepción Match_failure .

Módulos

Los módulos permiten dividir el programa en una jerarquía de estructuras que contienen tipos y valores relacionados lógicamente (por ejemplo, todas las funciones de manipulación de listas están en el módulo Lista). Los descendientes de la familia ML son los lenguajes que actualmente cuentan con los sistemas de módulos más sofisticados, que permiten, además de tener espacios de nombres, implementar abstracción (valores accesibles cuya implementación está oculta) y componibilidad (valores que pueden construirse sobre diferentes módulos, siempre que respondan a una interfaz determinada).

Así, las tres unidades sintácticas de construcción sintáctica son estructuras, interfaces y módulos. Las estructuras contienen la implementación de módulos, las interfaces describen los valores que son accesibles desde ellas (los valores cuya implementación no se expone son valores abstractos, y los que no aparecen en absoluto en la implementación del módulo. Son inaccesibles, como métodos privados en programación orientada a objetos). Un módulo puede tener múltiples interfaces (siempre que todas sean compatibles con los tipos de implementación) y múltiples módulos pueden verificar una sola interfaz. Los funciones son estructuras parametrizadas por otras estructuras; por ejemplo, las tablas hash (módulo Hashtbl) de la librería OCaml estándar se pueden utilizar como functor, que toma como parámetro cualquier estructura que implemente la interfaz compuesta por un tipo, una función de igualdad entre las claves y una función hash .

Orientado a objetos

OCaml se distingue particularmente por su extensión de la tipificación ML a un sistema de objetos comparable a los utilizados por los lenguajes de objetos clásicos. Esto permite el subtipo estructural , en el que los objetos son de tipos compatibles si los tipos de sus métodos son compatibles, independientemente de sus respectivos árboles de herencia . Esta funcionalidad, que puede considerarse como el equivalente a la tipificación pato de lenguajes dinámicos, permite una integración natural de conceptos de objeto en un lenguaje generalmente funcional.

Por lo tanto, a diferencia de los lenguajes orientados a objetos como C ++ o Java para los que cada clase define un tipo, las clases OCaml definen más bien abreviaturas de tipos. De hecho, siempre que el tipo de métodos sea compatible, dos objetos de dos clases diferentes pueden usarse indistintamente en el mismo contexto. Esta característica de la capa de objetos de OCaml rompe una buena cantidad de principios comúnmente aceptados: de hecho, es posible realizar subtipos sin herencia, por ejemplo. El lado polimórfico rompe el principio inverso. También existen ejemplos de código, aunque raros, que muestran casos de herencia sin subtipificación. La fuerza de la capa de objetos radica en su homogeneidad y su perfecta integración en la filosofía y el espíritu mismo del lenguaje OCaml. También son posibles los objetos funcionales, cuyos atributos no se pueden modificar y cuyos métodos, en su caso, devuelven una copia con la actualización de los atributos, o la definición de objetos inmediatos, o sobre la marcha.

Distribución

La distribución OCaml contiene:

  • un intérprete interactivo (ocaml);
  • un código de bytes compilador (ocamlc) y bytecode intérprete (ocamlrun);
  • un compilador nativo (ocamlopt);
  • generadores de analizadores léxicos (ocamllex) y sintácticos (ocamlyacc);
  • un preprocesador (camlp4), que permite extensiones o modificaciones de la sintaxis del lenguaje;
  • un depurador paso a paso, con reversión (ocamldebug);
  • herramientas de perfilado  ;
  • un generador de documentación (ocamldoc);
  • un administrador de compilación automático (ocamlbuild), desde OCaml 3.10;
  • una biblioteca estándar variada.

Las herramientas OCaml se utilizan habitualmente en Windows , GNU / Linux o MacOS , pero también existen en otros sistemas como BSD .

El código de bytes compilador le permite crear archivos que luego son interpretadas por ocamlrun. El código de bytes plataforma de ser independiente, lo que garantiza una gran portabilidad (ocamlrun puede a priori ser compilado en cualquier plataforma que soporte un compilador C funcional). El compilador nativo produce un código ensamblador específico de la plataforma, que sacrifica la portabilidad del ejecutable producido para un rendimiento muy mejorado. Hay un compilador nativo para las plataformas IA-32 , PowerPC , AMD64 , Alpha , Sparc , Mips , IA-64 , HPPA y StrongARM .

Una interfaz de compatibilidad le permite vincular código OCaml a primitivas C , y el formato de matrices de punto flotante es compatible con C y Fortran . OCaml también permite la integración de código OCaml en un programa C, lo que hace posible distribuir bibliotecas OCaml a programadores C sin que ellos necesiten conocer o incluso instalar OCaml.

Las herramientas OCaml se codifican principalmente en OCaml, con la excepción de algunas bibliotecas y el intérprete de código de bytes , que están codificados en C. En particular, el compilador nativo está completamente codificado en OCaml.

Gestión de la memoria

OCaml tiene, como Java, una gestión de memoria automatizada, gracias a una recolección de basura incremental generacional. Esto está especialmente adaptado a un lenguaje funcional (optimizado para una rápida tasa de asignación / liberación de objetos pequeños) y, por lo tanto, no tiene un impacto apreciable en el rendimiento de los programas. Se puede configurar para que siga siendo eficiente en situaciones de uso de memoria atípicas.

Actuaciones

OCaml se diferencia de la mayoría de los lenguajes desarrollados en círculos académicos por su excelente desempeño. . Además de las optimizaciones locales "clásicas" llevadas a cabo por el generador de código nativo , las actuaciones se benefician ventajosamente de la naturaleza funcional y estática y fuertemente tipada del lenguaje.

Por lo tanto, la información de tipeo está completamente determinada en tiempo de compilación y no necesita ser reproducida en el código nativo, lo que permite, entre otras cosas, eliminar completamente las pruebas de tipeo en tiempo de ejecución. Por otro lado, algunos algoritmos de la biblioteca estándar explotan las interesantes propiedades de las estructuras de datos funcionales puras: así, el algoritmo de unión de conjuntos es asintóticamente más rápido que el de los lenguajes imperativos, porque utiliza su no mutabilidad. conjuntos para constituir el conjunto de salida (esta es la técnica de copia de ruta para estructuras de datos persistentes).

Históricamente, los lenguajes funcionales han sido considerados lentos por algunos programadores, porque naturalmente requieren la implementación de conceptos (recuperación de memoria, aplicación parcial, etc.) que no sabíamos cómo compilar de manera eficiente; Desde entonces, los avances en las técnicas de compilación han hecho posible ponerse al día con la ventaja inicial de los lenguajes imperativos. OCaml, al optimizar de manera eficiente estas partes del lenguaje e implementar un recolector de basura adaptado a asignaciones frecuentes de lenguajes funcionales, fue uno de los primeros lenguajes funcionales en demostrar la eficiencia redescubierta de la programación funcional.

En general, la velocidad de ejecución es ligeramente menor que la de un código equivalente en C . Xavier Leroy habla con cautela de "rendimiento de al menos el 50% del de un compilador C razonable". Desde entonces, estos pronósticos han sido confirmados por numerosos puntos de referencia. En la práctica, los programas generalmente permanecen dentro de este rango (1 a 2 veces mayor que el código C), con extremos en ambas direcciones (a veces más rápido que C, a veces mucho más lento por una interacción desafortunada con el recolector de basura En cualquier caso, esto es aún más rápido que los lenguajes más recientes que no se compilan de forma nativa, como Python o Ruby , y comparable a los lenguajes estáticos compilados sobre la marcha como Java o C # .

usar

El lenguaje OCaml, resultado de los círculos de investigación, no se beneficia del poder publicitario de ciertos lenguajes de programación actuales. Por lo tanto, sigue siendo relativamente poco conocido por el público informático en general (así como por la mayoría de los lenguajes funcionales), pero, sin embargo, está firmemente establecido en unos pocos nichos en los que las cualidades del lenguaje tienen prioridad sobre su falta de popularidad.

Educación

OCaml es el idioma utilizado por las clases preparatorias de francés, donde sucedió a Caml Light, con miras a las pruebas de opción informática para los exámenes de acceso a las grandes écoles. Los documentos utilizados en el marco de esta enseñanza destacan los vínculos entre la programación funcional y las matemáticas, y la facilidad con la que los lenguajes de la familia ML manejan estructuras de datos recursivas, útiles para la enseñanza de algoritmos .

Sufre en el ámbito académico la competencia del lenguaje Haskell , que se le prefiere en algunos cursos de programación funcional, porque entre otras cosas, no retoma ningún concepto de programación imperativa .

Investigar

OCaml es un lenguaje bastante utilizado en el mundo de las búsquedas. Históricamente, los lenguajes de la rama ML siempre han estado estrechamente vinculados al campo de los sistemas de prueba formales (por lo tanto, el ML inicial de Robin Milner parecía usarse en el sistema de prueba LCF). OCaml es el lenguaje utilizado por uno de los principales software en el campo, el asistente de pruebas Coq .

OCaml participa en muchas otras áreas de la investigación en ciencias de la computación, incluida la investigación en lenguajes de programación y compiladores (consulte la sección Idiomas derivados ) o el software de sincronización de archivos Unison .

Industria

A pesar de su comunicación relativamente tímida, OCaml ha construido una base de usuarios sólida en áreas específicas de la industria. Así, la industria aeronáutica utiliza OCaml por su confiabilidad de programación y su eficiencia para la formulación de algoritmos complejos. En este ámbito, podemos citar el proyecto ASTRÉE , utilizado entre otros por la empresa Airbus . El compilador del lenguaje de programación en tiempo real síncrono Lustre , utilizado para sistemas críticos como los sistemas de aviónica de Airbus o el control de ciertas centrales nucleares, también está escrito en OCaml.

OCaml es utilizado por los principales actores de la industria del software, como Microsoft o XenSource , ambos miembros del Consorcio Caml. También encuentra aplicaciones en informática financiera, como lo demuestra la empresa Jane Street , que emplea a muchos programadores OCaml, o Lexifi, empresa francesa especializada en el diseño de lenguajes de programación dedicados a las finanzas, que además ha sido premiada internacionalmente.

Finalmente, también es utilizado por proyectos libres generalistas, como MLDonkey , GeneWeb , el cliente de radio web Liquidsoap , la biblioteca FFTW, así como algunos programas para el entorno de escritorio KDE . Finalmente, las fórmulas matemáticas del software MediaWiki son generadas por un programa escrito en OCaml.

Presentación del idioma

Bonjour Monde

Considere el siguiente programa hello.ml:

print_endline "Hello world!"

Puede ser compilado en ejecutable de código de bytes con el ocamlc código de bytes del compilador:

$ ocamlc -o hello hello.ml

También se puede compilar en un ejecutable de código nativo optimizado con el compilador nativo de ocamlopt:

$ ocamlopt -o hello hello.ml

Entonces, el programa puede ser ejecutado por el ocamlrun código de bytes intérprete:

$ ./hello Hello world!

o

$ ocamlrun hello Hello world!

Variables

Se puede utilizar el intérprete interactivo ocaml. Lanza un símbolo del sistema "#" después del cual se pueden ingresar las instrucciones OCaml, terminadas por caracteres ;;(estos caracteres de fin de instrucción solo deben usarse en el intérprete interactivo, no son parte de la sintaxis del idioma). Por ejemplo, para definir una variable que xcontenga el resultado del cálculo 1 + 2 * 3, escribimos:

$ ocaml # let x = 1 + 2 * 3;;

Después de ingresar y validar esta expresión, OCaml determina el tipo de expresión (en este caso, es un número entero) y muestra el resultado del cálculo:

val x : int = 7

Uno puede tener la tentación de realizar todo tipo de cálculos. Sin embargo, tenga cuidado de no mezclar enteros y reales, lo cual se hace comúnmente en muchos idiomas, porque en OCaml no se convierten automáticamente (debe usar una función que tomará un entero y devolverá un real, o al revés si el operador usa enteros). En el siguiente ejemplo, el operador + espera sumar dos enteros, cuando son dos reales:

# 2.3 + 1.;; Error: This expression has type float but an expression was expected of type int

Este sencillo ejemplo da una primera idea de cómo funciona el algoritmo de inferencia de tipos. De hecho, cuando escribimos 2.3 + 1., sumamos los números reales 2.3y 1.con el operador +entero, lo que plantea un problema. De hecho, para realizar este cálculo tenemos que asegurarnos de que todos los números tengan el mismo tipo por un lado (por ejemplo es imposible sumar 2.3y 1porque 1es un entero distinto de 1.o 2.3), y por otro lado usar la ley de composición interna +aplicada a números reales, anotada +.en OCaml. Entonces deberíamos haber escrito:

# 2.3 +. 1.;; - : float = 3.3

Funciones

Los programas suelen estar estructurados en procedimientos y funciones. Los procedimientos se componen de un conjunto de comandos que se utilizan varias veces en el programa y se agrupan por conveniencia bajo el mismo nombre. Un procedimiento no devuelve un valor, este rol se asigna a funciones. Muchos lenguajes tienen palabras clave distintas para introducir un nuevo procedimiento o una nueva función ( procedimiento y función en Pascal , sub y función en Visual Basic …). OCaml, por otro lado, solo tiene funciones, y estas se definen de la misma manera que las variables. Por ejemplo, para definir la identidad, podemos escribir:

# let id x = x;;

Después de ingresar y validar la expresión, el algoritmo de síntesis de tipos determina el tipo de función. Sin embargo, en el ejemplo que hemos dado, nada predice el tipo de x, por lo que la función aparece como polimórfica (a cualquier elemento del conjunto 'a, asocia una imagen id xque es un elemento del conjunto 'a):

val id : 'a -> 'a = <fun>

Para llamar a una función usamos la siguiente sintaxis:

# id 5;; - : int = 5

OCaml también permite el uso de funciones anónimas, es decir funciones no vinculadas a un identificador, gracias a la palabra clave functiono fun :

# function x -> x;; - : 'a -> 'a = <fun>

Las funciones anónimas se pueden llamar inmediatamente o usar para definir una función:

# (function x -> x + 1) 4;; - : int = 5 # let id = function x -> x;; val id : 'a -> 'a = <fun>

Filtrado de patrones

Una característica poderosa de OCaml es la coincidencia de patrones . Se puede definir con las palabras clave match witho con una función anónima, seguida para cada patrón por una barra vertical |, el patrón, una flecha ->y el valor de retorno:

# let est_nul x = # match x with # | 0 -> true (* la première barre verticale est facultative *) # | _ -> false;; val est_nul : int -> bool = <fun> # function # | 0 -> true (* la première barre verticale est facultative *) # | _ -> false;; - : int -> bool = <fun>

El guión bajo _representa el patrón predeterminado. Es posible dar el mismo valor a varios patrones a la vez:

# let contient_zero (x, y) = # match x, y with # | (0, _) | (_, 0) -> true # | _ -> false;; val contient_zero : int * int -> bool = <fun>

La palabra clave se whenusa para expresar una condición en el patrón:

# let contient_zero (x, y) = # match x, y with # | (x, y) when (x = 0 || y = 0) -> true # | _ -> false;; val contient_zero : int * int -> bool = <fun>

Los caracteres se ..utilizan para expresar un patrón que filtra los rangos de caracteres:

# let est_capitale c = # match c with # | 'a'..'z' -> false # | 'A'..'Z' -> true # | _ -> failwith "lettre invalide";; val est_capitale : char -> bool = <fun>

La palabra clave se asusa para nombrar el valor filtrado:

# let mettre_en_capitale c = # match c with # | 'a'..'z' as lettre -> Char.uppercase_ascii lettre # | 'A'..'Z' as lettre -> lettre # | _ -> failwith "lettre invalide";; val mettre_en_capitale : char -> char = <fun>

Recursividad

La recursividad consiste en escribir una función que se refiera a sí misma, sobre el modelo de inducción matemática. En OCaml, las funciones recursivas se introducen mediante la palabra clave rec.

Por ejemplo, podemos definir la función factorial  :

# let rec fact n = # match n with # | 0 -> 1 # | _ -> n * fact (n - 1);; val fact : int -> int = <fun>

Podemos definir la secuencia de Fibonacci por:

# let rec fib n = # match n with # | 0 -> 0 # | 1 -> 1 # | _ -> fib (n - 1) + fib (n - 2);; val fib : int -> int = <fun>

Definición interna

Es posible definir variables y funciones dentro de una función usando la palabra clave in.

Por ejemplo, podemos definir la función factorial:

# let fact n = # let rec fact_aux m a = # match m with # | 0 -> a # | _ -> fact_aux (m - 1) (m * a) # in fact_aux n 1;; val fact : int -> int = <fun>

Podemos definir la secuencia de Fibonacci por:

# let fib n = # let rec fib_aux m a b = # match m with # | 0 -> a # | _ -> fib_aux (m - 1) b (a + b) # in fib_aux n 0 1;; val fib : int -> int = <fun>

Recursividad terminal

El compilador OCaml optimiza las llamadas de terminal: cuando, durante la evaluación de una función, el último paso a realizar es la llamada de una (otra) función, OCaml salta directamente a esta nueva función sin mantener la llamada en memoria. De la primera, ahora inútil.

En particular, OCaml optimiza la recursividad terminal . Por ejemplo, la segunda función factanterior (con un parámetro auxiliar a) es una función terminal, equivalente a un bucle, y producirá un resultado equivalente, coincidiendo así con el rendimiento del código imperativo correspondiente.

La recursividad terminal es tan eficiente como la iteración; por lo tanto, es preferible cuando le permite escribir programas más claros o más fáciles de manejar.

Manipular listas

Las listas se utilizan en programación, especialmente para procesamiento recursivo. Todos los elementos de una lista son del mismo tipo. Para construir una lista, son posibles dos escrituras, una con el operador de encadenamiento ::y la otra con el operador ; :

# 1 :: 2 :: 3 :: [];; - : int list = [1; 2; 3] # [1; 2; 3];; - : int list = [1; 2; 3]

El operando derecho del último operador de encadenamiento de ::una expresión debe ser una lista:

# 1 :: 2 :: 3 :: [4; 5];; - : int list = [1; 2; 3; 4; 5]

Es posible concatenar listas con el operador de concatenación @ :

# [1; 2; 3] @ [4; 5];; - : int list = [1; 2; 3; 4; 5]

Para conocer la longitud de una lista sin utilizar la función List.lengthdefinida para tal fin, podemos escribir:

# let rec longueur l = # match l with # | [] -> 0 # | _ :: q -> 1 + longueur q;; val longueur : 'a list -> int = <fun>

Al analizar esta función mediante el algoritmo de inferencia de tipos, parece que la lista puede contener cualquier tipo de datos 'a.

La siguiente función crea una lista de parejas a partir de dos listas: la longitud de esta lista será igual a la longitud de la lista pasada en el parámetro que es el más corto.

# let rec couple l1 l2 = # match l1, l2 with # | ([], _) | (_, []) -> [] # | (t1 :: q1, t2 :: q2) -> (t1, t2) :: couple q1 q2;; val couple : 'a list -> 'b list -> ('a * 'b) list = <fun>

La siguiente función recupera el primer elemento de una lista:

# let premier_element l = # match l with # | [] -> failwith "liste vide" # | e :: _ -> e;; val premier_element : 'a list -> 'a = <fun>

La siguiente función recupera el segundo elemento de una lista:

# let deuxieme_element l = # match l with # | [] -> failwith "liste vide" # | [_] -> failwith "liste à un élément" # | _ :: e :: _ -> e;; val deuxieme_element : 'a list -> 'a = <fun>

La siguiente función recupera el primer elemento de la primera sublista:

# let sous_premier_element l = # match l with # | [] -> failwith "liste vide" # | [] :: _ -> failwith "sous liste vide" # | (e :: _) :: _ -> e;; val sous_premier_element : 'a list list -> 'a = <fun>

Funciones de orden superior

Las funciones de orden superior son funciones que toman una o más funciones en entrada y / o devuelven una función (esto se llama funcional). La mayoría de los lenguajes funcionales tienen funciones de orden superior. En cuanto a OCaml, se pueden encontrar ejemplos en las funciones de los módulos predefinidos Array, Listetc. Por ejemplo, la siguiente expresión:

# List.map (function i -> i * i) [0; 1; 2; 3; 4; 5];; - : int list = [0; 1; 4; 9; 16; 25]

La función maptoma como argumento la función anónima que, en su conjunto i, asocia su cuadrado, y lo aplica a los elementos de la lista, construyendo así la lista de los valores al cuadrado.

Otro ejemplo :

# let double f i = f (f i);; val double : ('a -> 'a) -> 'a -> 'a = <fun>

La función doubletoma una función fy un valor como parámetro iy se aplica dos veces fa i.

# let trois = double (function i -> i + 1) 1;; val trois : int = 3 # let augmente_2 = double (function i -> i + 1);; val augmente_2 : int -> int = <fun> # let liste = # double ( # function # | [] -> [] # | e :: l -> (e + 1) :: l # ) [1; 2; 3];; val liste : int list = [3; 2; 3]

Aquí hay un ejemplo más:

# let rec parcours f e l = # match l with # | [] -> e # | t :: q -> f t (parcours f e q);; val parcours : ('a -> 'b -> 'b) -> 'b -> 'a list -> 'b = <fun> # (* somme des éléments de la liste [1; 1; 2] *) # parcours (+) 0 [1; 1; 2];; - : int = 4 # (* fonction calculant la somme des éléments d'une liste *) # let somme_liste = parcours (+) 0;; val somme_liste : int list -> int = <fun> # (* fonction calculant le produit des éléments d'une liste *) # let produit_liste = parcours ( *. ) 1.;; val produit_liste : float list -> float = <fun>

Finalmente, un último ejemplo. Aquí, somos responsables de definir un nuevo operador destacado $. Este operador realiza la combinación de dos funciones.

# let ( $ ) f g = function x -> f (g x) val ( $ ) : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun> # let f x = x * x;; val f : int -> int = <fun> # let g x = x + 3;; val g : int -> int = <fun> # let h = f $ g;; val h : int -> int = <fun> # (* affiche 36 *) # print_int (h 3);;

Árboles y tipos recursivos

Para definir un árbol binario de cualquier tipo, usamos un tipo recursivo. Por tanto, podemos recurrir al siguiente escrito:

# type 'a arbre = # | Feuille # | Branche of 'a arbre * 'a * 'a arbre;; type 'a arbre = Feuille | Branche of 'a arbre * 'a * 'a arbre

Este árbol está formado por ramas que se ramifican a voluntad y terminan en hojas. Para saber la altura de un árbol, usamos:

# let rec hauteur = # function # | Feuille -> 0 # | Branche (gauche, _, droite) -> 1 + max (hauteur gauche) (hauteur droite);; val hauteur : 'a arbre -> int = <fun>

Búsqueda de raíces por dicotomía

# let rec dicho f min max eps = # let fmin = f min and fmax = f max in # if fmin *. fmax > 0. then failwith "Aucune racine" # else if max -. min < eps then (min, max) (* retourne un intervalle *) # else let mil = (min +. max)/.2. in # if (f mil) *. fmin < 0. then dicho f min mil eps # else dicho f mil max eps;; val dicho : (float -> float) -> float -> float -> float -> float * float = <fun> (* approximation de la racine carrée de 2 *) # dicho (function x -> x *. x -. 2.) 0. 10. 0.000000001;; - : float * float = (1.4142135618, 1.41421356238)

Memoisation

A continuación, se muestra un ejemplo de una función que utiliza la memorización . Esta es una función que calcula el enésimo término de la secuencia de Fibonacci . A diferencia de la clásica función recursiva, esta memoriza los resultados y puede sacarlos en tiempo constante gracias a la tabla hash.

(* Taille de la table de hachage. *) let _HASH_TABLE_SIZE = 997 (* Retourne le n-ième terme de la suite de Fibonacci. *) let rec fibo = (* Pré-définitions. Ce code est exécuté une fois lors de la définition de la fonction, mais ne l'est pas à chaque appel. Cependant, `h` reste dans l'environnement de cette fonction pour chaque appel. *) let h = Hashtbl.create _HASH_TABLE_SIZE in (* Premiers termes. *) Hashtbl.add h 0 0; Hashtbl.add h 1 1; function | n when n < 0 -> invalid_arg "fibo" (* Pas de nombre négatif. *) | n -> try Hashtbl.find h n (* On a déjà calculé `fibo n`, on ressort donc le résultat stocké dans la table `h`. *) with Not_found -> (* Si l'élément n'est pas trouvé, … *) let r = fibo (n - 1) + fibo (n - 2) in (* … on le calcule, … *) Hashtbl.add h n r; (* … on le stocke, … *) r (* … et on renvoie la valeur. *)

Derivación de un polinomio

Proponemos aquí implementar una función simple que permita derivar un polinomio de cualquier grado. Primero debemos especificar el tipo que representará nuestro polinomio:

# type polyn = # | Num of float (* constante *) # | Var of string (* variable *) # | Neg of polyn (* négation *) # | Add of polyn * polyn (* addition *) # | Sub of polyn * polyn (* soustraction *) # | Mul of polyn * polyn (* multiplication *) # | Div of polyn * polyn (* division *) # | Pow of polyn * int;; (* exponentiation *) type polyn = Num of float | Var of string | Neg of polyn | Add of polyn * polyn | Sub of polyn * polyn | Mul of polyn * polyn | Div of polyn * polyn | Pow of polyn * int

Ahora, aquí está la función que deriva este polinomio con respecto a la variable x especificada en parámetro.

# let rec deriv x = function # | Num _ -> Num 0. # | Var y when y = x -> Num 1. # | Var _ -> Num 0. # | Neg p -> Neg (deriv x p) (* -p' *) # | Add (p, q) -> Add (deriv x p, deriv x q) (* p' + q' *) # | Sub (p, q) -> Sub (deriv x p, deriv x q) (* p' - q' *) # | Mul (p, q) -> Add (Mul (deriv x p, q), Mul (p, deriv x q)) (* p'q + pq' *) # | Div (p, q) -> Div (Sub (Mul (deriv x p, q), Mul (p, deriv x q)), Pow (q, 2)) (* (p'q - pq')/q^2 *) # | Pow (p, 0) -> Num 0. # | Pow (p, 1) -> deriv x p # | Pow (p, n) -> Mul (Num (float_of_int n), Mul (deriv x p, Pow (p, n - 1))) (* n * p' * p^(n - 1) *) val deriv : string -> polyn -> polyn = <fun>

Diferentes objetivos de compilación

La implementación de OCaml ha sido adaptada por otros autores para objetivos de compilación distintos del código de bytes y el código nativo. Lo encontraremos :

  • OCaml-Java, una distribución para la JVM que contiene ocamlc, ocamlrun, ocamldep, ocamldoc, ocamllex, menhir, un compilador ocamljava para la JVM;
  • OCamIL, un prototipo de backend para el entorno .NET. Contiene un compilador para OCaml código de bytes (ejecutables por ocamlrun), un compilador para .NET y una herramienta como ocamlyacc llamado ocamilyacc.

Idiomas derivados

Muchos lenguajes extienden OCaml para agregarle funcionalidad.

  • F # es un lenguaje de la plataforma .NET desarrollado por Microsoft Research, basado en OCaml (y parcialmente compatible).
  • MetaOCaml agrega un mecanismo de generación de códigos y citas al tiempo de ejecución , lo que trae la funcionalidad de metaprogramación a OCaml.
  • Fresh OCaml (basado en AlphaCaml, otro derivado de OCaml) facilita la manipulación de nombres simbólicos.
  • JoCaml se suma al soporte de OCaml para Join Calculus, orientado a programas concurrentes o distribuidos.
  • OcamlP3L trae una forma particular de paralelismo, basada en la “ programación esqueleto” .
  • GCaml agrega polimorfismo ad-hoc a OCaml, lo que permite al operador sobrecargar o ordenar y preservar la información de escritura.
  • OCamlDuce permite que el sistema de tipos represente valores XML o relacionados con expresiones regulares. Es un intermediario entre OCaml y el lenguaje CDuce, especializado en la manipulación de XML.
  • Opa es un lenguaje para el desarrollo de aplicaciones y servicios web implementado en OCaml y cuyo núcleo incorpora las funcionalidades del lenguaje OCaml. Además, el compilador Opa utiliza el backend del compilador OCaml para generar servidores nativos.
  • ReasonML, desarrollado por Facebook, es un lenguaje que utiliza el compilador OCaml, que permite la compilación de código Reason (.re) y OCaml (.ml) en código fuente JavaScript (.js), que a su vez se puede compilar en código byte, utilizado en el navegador web o mediante un intérprete como Node.js.

Notas y referencias

  1. "  Messenger.com Now 50% Converted to Reason · Reason  " , en whyml.github.io (consultado el 27 de febrero de 2018 )
  2. Algunos éxitos de OCaml: SLAM
  3. "Los programas funcionales son programas que generalmente asignan mucho y vemos que muchos valores tienen una vida útil muy corta". Por otro lado, tan pronto como un valor ha sobrevivido a varios GC, tiene muchas posibilidades de existir durante un buen tiempo ”- Desarrollo de aplicaciones con Objective Caml
  4. "La  generación de código de máquina eficiente siempre ha sido un aspecto importante de OCaml, y dediqué bastante trabajo a esto al comienzo del desarrollo de OCaml (95-97). Hoy en día, estamos ampliamente satisfechos con el rendimiento del código generado.  »- Xavier Leroy, en la lista de correo de caml .
  5. "Las  garantías proporcionadas por el sistema de tipos también pueden permitir potentes optimizaciones del programa.  »- Xavier Leroy, Introducción a los tipos en la compilación .
  6. (en) hilo de la lista de correo  : "  Nuestra declaración de rendimiento general" OCaml ofrece al menos el 50% del rendimiento de un compilador de C decente "no se invalida  :-)"
  7. (en) tiroteo: OCaml vs. C de referencia .
  8. comparación de rendimiento entre ocamlopt y C # Mono .
  9. "  Memorándum ESRS1732186N del 27 de noviembre de 2017  "
  10. Enseñar programación con Caml
  11. Citeseer: una lista de artículos de investigación que utilizan Caml
  12. Sitio del proyecto ASTRÉE , consultado en www.astree.ens.fr en15 de enero de 2011
  13. "Interpretación abstracta: aplicación al software del A380" , Patrick Cousot, consultado en www.di.ens.fr le15 de enero de 2011
  14. Consorcio Caml
  15. La biblioteca FFTW, realizando una transformada rápida de Fourier , se compone de código en C lenguaje . Sin embargo, por razones de rendimiento, el código C es generado y optimizado automáticamente por un compilador, genfft , escrito en OCaml. El proceso de generación y especialización de rutinas se describe en el artículo A Fast Fourier Transform Compiler , de Matteo Frigo (MIT) [ leer en línea  (página consultada el 9 de diciembre de 2007)] . Encontramos en la documentación de FFTW una apreciación sobre el uso de OCaml: “  El conjunto de generadores de código genfft fue escrito usando Objective Caml, un dialecto de ML. Objective Caml es un lenguaje pequeño y elegante desarrollado por Xavier Leroy. La implementación está disponible en http://caml.inria.fr/ . En versiones anteriores de FFTW, genfft fue escrito en Caml Light, por los mismos autores. Una puesta en práctica incluso antes de genfft fue escrito en el esquema , pero Caml es definitivamente mejor para este tipo de aplicación.  "- Agradecimientos de FFTW
  16. Página del componente EqChem del software Kalzium
  17. Un programa que usa llamadas de terminal es a menudo más legible que una iteración equivalente cuando el patrón de salto es complejo. Por ejemplo, podemos describir un autómata como un conjunto de funciones de transición que realizan llamadas de terminal (o saltos gotoen lenguajes imperativos) a los otros estados del autómata. En este caso, las llamadas al terminal permiten una mayor flexibilidad, como se muestra en el artículo (in) Automatas Via Macros .

Ver también

Artículos relacionados

enlaces externos