OCaml | |
---|---|
Semántica | multiparadigma : funcional , orientado a objetos , imperativo |
clase de idioma | lenguaje de programación orientado a objetos , lenguaje de programación funcional , lenguaje de programación multiparadigma , lenguaje de programación imperativo , lenguaje dey software libre y de código abierto |
Apareció en | 1996 |
Autor | Leroy, Xavier y Damián Doligez [d] |
Desarrollador | INRIA |
extensión de archivo | .ml, .mli |
Liberar | 4.14.0 ( 28 de marzo de 2022 ) |
sistema de tipos | estricto , estático |
Dialectos | F# , JoCaml , MetaOCaml, OcamlP3l |
sido influenciado | ML estándar , Caml Light |
Licencia | LGPL |
Sitio web | ocaml.org |
sistema operativo | Sistema operativo similar a Unix [1] |
Archivos multimedia en Wikimedia Commons |
OCaml ( Objetivo Caml ) es un lenguaje de programación funcional orientado a objetos de propósito general . Fue diseñado teniendo en cuenta la seguridad de ejecución y la confiabilidad de los programas. Admite paradigmas de programación funcional, imperativa y orientada a objetos. El dialecto más común del lenguaje ML en el trabajo práctico .
Apareció en 1996 bajo el nombre Objective Caml, cuando Didier Rémy (Didier Rémy) y Jérôme Vouillon (Jérôme Vouillon) implementaron soporte para la programación orientada a objetos para el lenguaje Caml, originalmente desarrollado en el Instituto Francés INRIA . Oficialmente renombrado a OCaml en 2011 [2] .
El kit de herramientas de OCaml incluye un intérprete , un compilador de código de bytes y un compilador de optimización de código nativo, comparable en eficiencia a Java y solo ligeramente inferior en rendimiento a C y C++ [3] .
En particular, la representación de fórmulas de Wikipedia usando la etiqueta <math>, el cliente de intercambio de archivos MLDonkey , la pila de control del hipervisor Xen xapi (parte del servidor Xen/Xen Cloud Platform) y el lenguaje de programación Haxe están escritos en OCaml. lenguaje
El lenguaje OCaml es un lenguaje de programación de propósito general, pero tiene sus propias áreas de aplicación establecidas [4] .
Primero, es la creación de aplicaciones "seguras" (no solo en el sentido de seguridad de la información). El lenguaje utiliza la recolección de elementos no utilizados y la mayoría de los tipos de datos son de referencia ( casillados en inglés ), lo que significa que se evitan los desbordamientos del búfer durante la ejecución del programa. Además, la tipificación estática y las verificaciones en tiempo de compilación hacen que otras clases de errores, como los errores de conversión, sean imposibles debido a la falta de conversión automática de tipos. Además, el código se puede verificar formalmente . Existen utilidades para probar automáticamente la corrección del tipo del código, superiores a las de la mayoría de los lenguajes de programación. Y lo que es más importante, las medidas de seguridad no afectan la eficiencia del código ejecutable [4] .
Otra área de éxito con OCaml es en aplicaciones basadas en datos . Esta área incluye el procesamiento de texto, así como la escritura de compiladores. OCaml no solo tiene herramientas para el procesamiento de texto (que, por ejemplo, Perl o AWK son famosos ), sino también herramientas para el análisis semántico profundo y la transformación de texto, lo que hace que OCaml sea aplicable en tareas de minería de datos [ 4 ] .
Por supuesto, OCaml, al igual que otros dialectos de ML, se utiliza en tareas de investigación y verificación, en las que el código principal se escribe en algún lenguaje de programación y luego se verifica y analiza formalmente mediante un programa OCaml [4] . Por ejemplo, el sistema interactivo de prueba de teoremas Coq está escrito en OCaml .
OCaml ocupa un lugar especial entre los lenguajes de programación por su combinación de eficiencia, expresividad y practicidad. Entre las características del lenguaje que han evolucionado durante más de 40 años desde la creación de ML se encuentran [5] :
OCaml tiene su origen en ML ( eng. metalenguaje ), que fue implementado en el dialecto Lisp por Robin Milner en 1972 como una herramienta de software para probar teoremas, como metalenguaje para la lógica de funciones computables (LCF, eng. logic for computable funciones ). Más tarde, se creó un compilador y, en 1980, ML se había convertido en un sistema de programación completo [6] .
Guy Cousineau agregó tipos de datos algebraicos y coincidencia de patrones al lenguaje y definió ML como una máquina abstracta categórica (CAM). Por lo tanto, CAM-ML pudo describirse, verificarse y optimizarse, lo que supuso un paso adelante para ML [7] .
Otro desarrollo fue el lenguaje Caml (reproducido por CAM-ML) [6] [7] creado en 1987 por Ascánder Suárez y continuado por Pierre Weis y Michel Mauny .
En 1990, Xavier Leroy y Damien Doligez lanzaron una nueva implementación llamada Caml Light . Esta implementación de C usó un intérprete de código de bytes y un recolector de basura rápido. Con la escritura de las bibliotecas, el lenguaje comenzó a ser utilizado en institutos de educación e investigación [6] [7] .
Caml Special Light , desarrollado por C. Leroy , vio la luz en 1995 . El sistema de programación recibió un compilador en códigos de máquina, lo que puso la eficiencia del código ejecutable a la par con otros lenguajes compilados. Al mismo tiempo, se desarrolló un sistema de módulos , cuya idea se tomó prestada de Standard ML [6] .
La forma moderna de OCaml se remonta a 1996 , cuando Didier Rémy y Jérôme Vouillon implementaron un soporte de objetos limpio y eficiente para el lenguaje . Este sistema de objetos le permite utilizar lenguajes de programación orientados a objetos en tiempo de compilación de una manera segura , sin las comprobaciones inherentes de tiempo de ejecución de C++ y Java [6] .
En la década de 2000, el idioma ha evolucionado sin problemas, mientras ganaba más reconocimiento en proyectos comerciales y educativos. Entre los desarrollados en este momento se encuentran métodos polimórficos y tipos de variantes, parámetros con nombre y opcionales, módulos de primera clase , tipos de datos algebraicos generalizados (GADT). El lenguaje comenzó a admitir varias plataformas de hardware ( X86 , ARM , SPARC , PowerPC ) [6] [7] .
El modelo de cálculo de OCaml como lenguaje de programación funcional se basa en tres construcciones principales del cálculo lambda : variables , definiciones de función y aplicación de una función a argumentos [8] .
Una variable es un identificador cuyo valor está asociado a un valor determinado. Los nombres de las variables comienzan con una letra minúscula o un guión bajo. La vinculación generalmente se realiza con la palabra clave let, como en el siguiente ejemplo en un shell interactivo [9] :
sea v = 1 ;;Las variables tienen un ámbito . Por ejemplo, en un shell interactivo, se puede usar una variable en los comandos que siguen a su vinculación. De manera similar, una variable definida en un módulo se puede usar después de haber sido definida en ese módulo [9] .
La vinculación de variables también se puede realizar en el ámbito especificado por la construcción let-in, como en el siguiente ejemplo para calcular el área de un círculo a partir de un radio:
# sea el radio del área = sea pi = 3 . 14 en radio *. radio *. pi ;; val area : float -> float = < fun > # area 2 . 0 ;; - : flotador = 12 . 56En OCaml, los enlaces de variables son inmutables (como en las ecuaciones matemáticas), es decir, el valor de una variable se "asigna" solo una vez (asignación única). Otra cosa es que dentro de let-in pueda haber otro let-in, en el que se introduzca otra variable, que pueda “sombrear” a la primera [9] .
Hay varias construcciones de sintaxis para definir funciones en OCaml.
Las funciones se pueden definir usando el function. La expresión de la función se ve así [10] :
función x -> x + 1En este caso, la función es anónima y puede usarse como parámetro para otras funciones o aplicarse a algún argumento, por ejemplo:
( función x -> x + 1 ) 5El tipo de esta función es int -> int, es decir, la función toma un número entero y devuelve un número entero.
Una función puede tener múltiples argumentos [11] :
función ( x , y ) -> x - yEn este ejemplo, su tipo es: int * int -> int, es decir, la entrada de la función es un par y la salida es un número entero.
Hay otro enfoque para representar funciones de varios argumentos: convertir una función N-aria en N funciones de un argumento: curry . Las siguientes dos notaciones para una función que calcula el producto de argumentos enteros son equivalentes [11] :
función x -> función y -> x * y divertido x y -> x * yLas funciones con nombre se pueden obtener asociando una variable con una función [10] . La definición de una función con nombre es una operación tan común que tiene soporte sintáctico separado. Las siguientes tres entradas son formas equivalentes de definir una función (en un shell interactivo):
# let prod = función x -> función y -> x * y ;; val prod : int -> int -> int = < fun > # let prod x y = x * y ;; val prod : int -> int -> int = < diversión > # let prod = diversión x y -> x * y ;; val prod : int -> int -> int = < diversión >Las funciones de dos argumentos se pueden definir para usar la notación infija [10] :
# sea (^^) x y = x ** 2 . 0+ . y ** 2 . 0 ;; val ( ^^ ) : flotar -> flotar -> flotar = < diversión > # 2 . 0 ^^ 3 . 0 ;; - : flotante = 13 . # (^^) 2 . 0 3 . 0 ;; - : flotante = 13 .Este ejemplo define una función (^^)que calcula la suma de los cuadrados de dos números de coma flotante . Los dos últimos tipos de notación son equivalentes.
Las funciones recursivas , es decir, las funciones que se refieren a su propia definición, se pueden especificar usando let rec[10] :
# let rec fac n = emparejar n con | 0 -> 1 | x -> x * fac ( x - 1 ) ;;En el mismo ejemplo de cálculo factorial , se aplica la coincidencia de patrones (construcción match-with).
Los argumentos de función se pueden definir como nombrados. Los argumentos con nombre se pueden especificar en cualquier orden [10] :
# let divmod ~ x ~ y = ( x / y , x mod y ) ;; val divmod : x : int -> y : int -> int * int = < diversión > # divmod ~ x : 4 ~ y : 3 ;; - : int * int = ( 1 , 1 ) # divmod ~ y : 3 ~ x : 4 ;; - : int * int = ( 1 , 1 )En OCaml, puede omitir valores usando el juego de palabras de etiquetas si el nombre del parámetro y el nombre de la variable son iguales [ 10] :
# sea x = 4 in sea y = 3 in divmod ~ x ~ y ;; - : int * int = ( 1 , 1 )
La asociatividad de las operaciones en las expresiones OCaml está determinada por el prefijo, por lo que se extiende a las operaciones definidas por el usuario. El signo -funciona tanto como prefijo como operación de infijo, y si es necesario, para usarlo como prefijo junto con la función, el parámetro debe estar encerrado entre paréntesis [12] .
Prefijo de operación | Asociatividad |
---|---|
! ? ~ | Prefijo |
. .( .[ .{ | |
aplicando una función, constructor, etiqueta, assert,lazy | Izquierda |
- -. | Prefijo |
** lsl lsr asr | Derecha |
* / % mod land lor lxor | Izquierda |
+ - | Izquierda |
:: | Derecha |
@ ^ | Derecha |
& $ != | Izquierda |
& && | Derecha |
or || | Derecha |
, | |
<- := | Derecha |
if | |
; | Derecha |
let match fun function try |
El lenguaje OCaml tiene varios tipos primitivos : tipos numéricos ( entero y coma flotante), caracteres , cadenas de caracteres , booleanos [13] .
El tipo entero representa números enteros del rango [−2 30 , 2 30 − 1] y [−2 62 , 2 62 − 1] para arquitecturas de 32 y 64 bits, respectivamente. Con números enteros, puede realizar las operaciones habituales de suma, resta, multiplicación, división, tomando el resto de la división :+,-,*,/,mod. Si el resultado va más allá del intervalo permitido, no se produce ningún error y el resultado se calcula módulo el límite del intervalo [14] .
Los números de coma flotante están representados por una mantisa de 53 bitsy un exponente en el intervalo [−1022, 1023], siguiendo el estándar IEEE 754 para dobles. En operaciones, estos números no se pueden mezclar con enteros. Además, las operaciones con números de punto flotante son sintácticamente diferentes de las operaciones con enteros:+.,-.,*.,/.. También hay una operación de exponenciación:**. Para convertir números enteros a números de coma flotante y viceversa, están disponibles las siguientes funciones: float_of_int e int_of_float [14] .
Para los números de coma flotante, existen otras funciones matemáticas: trigonométricas (sin, cos, tan, asin, acos, atan), redondeo (techo, piso), exponencial (exp), logarítmica (log, log10), así como tomar el raíz cuadrada (raíz cuadrada) [14] . También hay operaciones de comparación polimórficas para tipos numéricos [14] .
El tipo de carácter - char - corresponde a la representación de un carácter con un código de 0 a 255 (los primeros 128 caracteres son los mismos que ASCII ). Tipo de cadena - cadena - secuencia de caracteres (longitud máxima: 2 24 - 6) [15] . Un ejemplo usando la función de conversión de entero a cadena y la operación de concatenación :
# "Ejemplo" ^ string_of_int ( 2 ) ;; - : cadena = "Ejemplo 2"El tipo booleano tiene dos valores:true(verdadero) yfalse(falso). Operaciones sobre valores booleanos: unario not (negación), binario:&&(y),||(o). Las operaciones binarias evalúan primero el argumento de la izquierda y el argumento de la derecha solo si es necesario [16] .
Los valores booleanos se obtienen como resultado de comparaciones: =(igualdad estructural), ==(identidad), <>(negación de igualdad estructural), !=(negación de identidad), <, >, <=, >=. Para los tipos primitivos, a excepción de las cadenas y los números de punto flotante, la igualdad estructural y la identidad coinciden, para otros tipos, los valores ubicados en la misma dirección en la memoria se consideran idénticos y, en la comparación estructural, los valores se verifican componente por componente. [16] .
Además, OCaml tiene una unidad de tipo especial, que tiene un solo valor: ()[16] .
ListasEn OCaml , una lista es una secuencia finita e inmutable de elementos del mismo tipo, implementada como una lista enlazada individualmente. El siguiente ejemplo demuestra la sintaxis de la lista [17] :
# [ 'a' ; 'b' ; 'c' ] ;; - : lista de caracteres = [ 'a' ; 'b' ; 'c' ] # 'a' :: ( 'b' :: ( 'c' :: [] )) ;; - : lista de caracteres = [ 'a' ; 'b' ; 'c' ] # 'a' :: 'b' :: 'c' :: [] ;; - : lista de caracteres = [ 'a' ; 'b' ; 'c' ] # [] ;; - : ' una lista = []La operación ::le permite construir una lista basada en el nuevo elemento y la cola de la lista anterior. En este caso, la lista "antigua" no cambia:
# sea lst = [ 1 ; 2 ] ;; val lst : int lista = [ 1 ; 2 ] # let lst1 = 0 :: lst ;; val lst1 : int lista = [ 0 ; 1 ; 2 ] # lst ;; - : int lista = [ 1 ; 2 ] # lista1 ;; - : int lista = [ 0 ; 1 ; 2 ] Ejemplo: calcular la suma de los elementos de una listaLa lista es uno de los principales tipos de datos en OCaml. El siguiente ejemplo de código define una función recursiva (tenga en cuenta la palabra clave rec) que itera sobre los elementos de una lista determinada y devuelve su suma:
let rec sum xs = emparejar xs con | [] -> 0 | x :: xs' -> x + suma xs' #suma[1;2;3;4;5];; - : int = 15Otra forma de calcular la suma es usar la función de resumen:
sea suma xs = Lista . doblar_izquierda (+ ) 0xs # suma [ 1 ; 2 ; 3 ; 4 ; 5 ];; - : int = 15 EntradasLos registros son un elemento importante en el sistema de tipos OCaml. Un registro es un conjunto de valores almacenados juntos, donde cada elemento del valor-registro es accesible por su nombre, el nombre del campo del registro. Un ejemplo de una declaración de tipo, vinculando un registro a una variable y accediendo a un campo de registro [18] :
# tipo de usuario = { inicio de sesión : cadena ; contraseña : cadena _ nick : cadena _ };; # let usr = { login = "miusuario" ; contraseña = "secreto" ; nick = "alias" ; } ;; val usr : usuario = { inicio de sesión = "miusuario" ; contraseña = "secreto" ; nick = "alias" } # usr . Nick ;; - : cadena = "alias"Cabe señalar que el compilador estableció automáticamente el tipo de la variable usr.
Al igual que con otros tipos, un tipo se puede parametrizar. Otras posibilidades de grabación [18] :
Un tipo de variante representa datos que pueden tomar varias formas, definidas por etiquetas explícitas. El siguiente ejemplo define un tipo para colores base [19] :
# tipo main_color = Rojo | verde | azul ;; # azul ;; - : main_color = Azul # ( Rojo , Azul ) ;; - : color_principal * color_principal = ( Rojo , Azul )En el ejemplo anterior, el tipo de variante se usa como el tipo enumerado . En OCaml, sin embargo, el tipo de variante es más rico, porque además de las etiquetas, también te permite especificar datos, por ejemplo:
# escriba color_scheme = RGB de int * int * int | CMYK de float * float * float * float ;; tipo color_scheme = RGB de int * int * int | CMYK de flotante * flotante * flotante * flotanteAl definir funciones, el tipo de variante se empareja naturalmente con la coincidencia de patrones.
ObjetosEn OCaml, los objetos y sus tipos están completamente separados del sistema de clases . Las clases se utilizan para construir objetos y admitir la herencia , pero no son tipos de objetos. Los objetos tienen sus propios tipos de objetos y no es necesario usar clases para trabajar con objetos. Los objetos no se usan con tanta frecuencia en OCaml (por ejemplo, el sistema de módulos es más expresivo que los objetos, ya que los módulos pueden incluir tipos, pero las clases y los objetos no). La principal ventaja de los objetos sobre los registros es que no requieren declaraciones de tipo y son más flexibles debido al polimorfismo de fila . Por otro lado, los beneficios de los objetos entran en juego cuando se utiliza el sistema de clases. A diferencia de los módulos, las clases admiten el enlace tardío, lo que le permite referirse a métodos de objetos sin una implementación definida estáticamente y usar recursividad abierta (en el caso de los módulos, puede usar funciones y funtores, pero sintácticamente tales descripciones requieren escribir más código) [20 ] .
Inferencia de tipoAunque OCaml es un lenguaje de programación fuertemente tipado , el sistema de inferencia de tipos ( type inference en inglés ) le permite determinar el tipo de una expresión en función de la información disponible sobre sus componentes. En el siguiente ejemplo de una función de paridad, no se especifica ninguna declaración de tipo y, sin embargo, el compilador del lenguaje tiene información completa sobre el tipo de la función [21] :
# sea impar x = x mod 2 <> 0 ;; valor impar : int -> bool = < divertido >Además de las funcionales, el lenguaje contiene herramientas de programación imperativas : funciones con efectos secundarios , datos mutables, construcciones sintácticas imperativas, en particular, bucles while explícitos y for[22] .
El siguiente ejemplo imprimirá 11 líneas en la salida estándar (este es un efecto secundario de la función printf):
para i = 0 a 10 hacer Printf . printf "i =%d \n " he terminado ;;En el siguiente ejemplo (bastante artificial), los elementos de una matriz se incrementan en su lugar en un bucle de condición previa. Para el índice de matriz, se utiliza una referencia (ref), que se incrementa en el cuerpo del ciclo:
# let incr_ar ar = let i = ref 0 in while ! yo < matriz . longitud ar do ar .(! i ) <- ar .(! i ) + 1 ; incr he hecho ;; val incr_ar : int array -> unit = < fun > # let nums = [| 1 ; 2 ; 3 ; 4 ; 5 |];; val nums : int array = [| 1 ; 2 ; 3 ; 4 ; 5 |] # incr_ar numeros ;; - : unidad = () # numeros ;; - : matriz int = [| 2 ; 3 ; 4 ; 5 ; 6 |]Los efectos secundarios le permiten optimizar los cálculos, especialmente cuando se trata de transformaciones significativas en grandes conjuntos de datos. También se utilizan para implementar evaluación y memorización perezosas [22] .
Se puede pensar que OCaml consta de dos lenguajes: un lenguaje central con valores y tipos, y un lenguaje de módulos y sus firmas . Estos lenguajes forman dos capas en el sentido de que los módulos pueden contener tipos y valores, mientras que los valores ordinarios no pueden contener módulos y módulos de tipos. Sin embargo, OCaml ofrece un mecanismo para módulos de primera clase , que pueden ser valores y convertir hacia y desde módulos normales según sea necesario [23] .
El sistema de módulos OCaml no se limita a la organización del código modular y las interfaces. Una de las herramientas importantes de la programación genérica son los funtores . En pocas palabras, los functores son una función de un módulo a módulos, lo que le permite implementar los siguientes mecanismos [24] :
Para iniciar el intérprete de lenguaje OCaml, ingrese el siguiente comando en la consola:
$ ocaml OCaml versión 4.08.1 #Los cálculos se pueden hacer de forma interactiva, por ejemplo:
# 1 + 2 * 3 ;; - : int = 7El siguiente programa "hello.ml":
print_endline "¡Hola mundo!" ;;se puede compilar en código de bytes :
$ ocamlc hola.ml -o holao en código de máquina optimizado :
$ ocamlopt hola.ml -o holay lanzó:
$ ./hola ¡Hola Mundo! psEl siguiente ejemplo es un algoritmo de ordenación rápida que ordena una lista en orden ascendente:
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 derechaNota : el libro utiliza la traducción del término " función de primera clase " como " función de primer orden ". Pero hay que tener en cuenta que en numerosas fuentes en lengua inglesa (sobre la semántica de los lenguajes en general y sobre ML y Hindley-Milner en particular) se distinguen conceptualmente cuatro conceptos:
además, " primera clase " es " mejor " que " segunda clase " (más amplia en capacidades, más cercana a la teoría y más alta en términos de umbral de entrada ( C. Strachey — Conceptos fundamentales en lenguajes de programación )), pero " de primer orden ". ” más primitivo que “de alto orden ”. En particular, extender el lenguaje del módulo ML al nivel de " primera clase de alto orden " presenta un problema mucho mayor para los investigadores que extenderlo solo a " primera clase " o solo a " alto orden " ( Rossberg A. Functors and tiempo de ejecución frente a tiempo de compilación (enlace descendente) Consultado el 25 de junio de 2015. Archivado desde el original el 26 de junio de 2015 ).
Lenguajes de programación | |
---|---|
|