La Gaceta de Linux ...¡ haciendo a Linux un poco más divertido !

OCaml, una Introducción

Por Jurjen Stellingwerff

Traducción al español por Rodolfo Franco
el día 21 de Mayo de 2004, para La Gaceta de Linux

Object Caml es un lenguage del tipo de ML. Para los no-gurus: es un lenguage funcional que también puede ser programado en una manera no funcional y orientada a objetos.

Este lenguage es realmente fácil de aprender. Es potente y me sigue impresionando con su velocidad. Los programas escritos en este lenguage son casi siempre estables por defecto. No hay fallos de segmento, sólo bucles infinitos ocasionales para los programadores que todavía se aferran a programar sus propios bucles. En realidad no es necesario programar la mayoría de los bucles, ya que las librerías contienen funciones estándar que son suficientemente buenas en el 99% de los casos. Entonces intente utilizar esas funciones: realmente compensa en términos de estabilidad de sus programas, y, a no ser que usted tenga un conocimiento íntimo del funcionamiento interno del lenguage, tienden a estar mejor optimizadas.

El lenguage se puede obtener del sitio caml.inria.fr. Aquí, ellos proveen RPMs para las distribuciones de RedHat 7.2/8.0/9 y Mandrake 8.0. También están disponibles los binarios para MS Windows, pero no todas las funciones de librería de Unix funcionarán ahí, por alguna razón misteriosa. El archivo tar con las fuentes compila sin problemas para mi. Solamente que el archivo makefile tiene un formato inusual:

# ./configure
# make world; make opt; make install

Las librerías normales incluyen muchas estructuras de datos usables como árboles balanceados, tablas de dispersión (hash), y flujos (streams). Su versión de los archivos de cabecera (archivos .mli) contienen toda la documentación básica que usted necesita, y son convertidos directamente a HTML y publicados en la Web en el manual de OCaml. Este manual no es muy usable para estudiar este lenguage, así que trataré de explicar aquí algunas de las construcciones básicas del lenguage. Esto es solamente para darle una impresión de la potencia de este lenguage.

Módulos & Funciones

Ahora algunos ejemplos de la vida real. He escrito un programa para ayudar a administrar una computadora. Es un subconjunto de un buscador de archivos normal, pero es una herramienta de línea de comandos y muy rápida. Ayuda a localizar archivos grandes, no recientemente usados para ser eliminados del sistema. Recorre el árbol de directorios y muestra el contenido en diferentes formatos.

Todo módulo en OCaml tiene su propio espacio de nombres. Las definiciones específicas pueden ser encontradas añadiendo el nombre del módulo, con el primer caracter en mayúsculas. También se puede cambiar el espacio de nombres del programa actual para incluir un módulo total. Normalmente, sólo el módulo estándar 'pervasives.mli' es incluído en el espacio de nombres por defecto. El programa de ejemplo 'show.ml' empieza con:

open Basics
open Unix
open Unix.LargeFile

Esto incluye mi propio juego de funciones 'básicas' y 2 librerías estándar: 'Unix' y 'Unix.LargeFile'. Un módulo consiste en 2 archivos. El primer archivo para exportar las definiciones 'modulo.mli' (como el archivo .h de C), y el segundo para el código real (el archivo 'modulo.ml'). El programa usa la función 'string_sub' que provee una versión a prueba de tontos de la función estándar de 'String.sub' (del módulo string.mli). El archivo basics.mli contiene las líneas:

val string_sub: string -> int -> int -> string
(** Obtiene una subcadena de una [cadena(string)] desde la posición [desde(int)] con la [longitud(int)].
Esta es la misma función que String.sub, pero nunca lanzará una excepción.
Si el valor [desde] es negativo se cuenta desde la parte derecha de la cadena. *)

Esto da la definición y la descripción de esta función. Hay un generador de documentación automática (ocamldoc) que lee archivos .mli y escribe archivos .html como documentación básica. Los comentarios normales empiezan con (* pero el generador de documentación sólo escribe comentarios que comienzan con (** en los archivos .html. Este documento contiene enlaces a la documentación de los módulos utilizados. Esta documentación es realmente útil para empezar a programar con ocaml. Los archivos .mli estan todos incluídos en la distribución, pero el manual completo y el libro puede ser descargado del sitio Web caml.inria.fr

La función es seguida por su tipo. Recibe 3 parámetros y devuelve una cadena. Normalmente necesitamos escribir 'Basics.string_sub' para usar esta función. Pero después de la instrucción 'open Basics' sólo 'string_sub' es suficiente.

Operaciones básicas y llamadas a funciones

Ahora, volviendo nuevamente al programa principal. La primera función es 'gettype'. Intentará devolver el tipo de un archivo. El tipo del archivo es definido como la parte del nombre a continuación del último '.'. Cuando no hay punto, el tipo es desconocido y devuelve vacío.

let gettype file =
try
let pos = String.rindex file '.' in
String.sub file (pos+1) (String.length file-pos-1)
with Not_found -> ""
;;

Esta función sólo usa funciones estándar. Primero, atrapa la excepción Not_found en el código 'try' 'with Not_found -> ""'. Todas las otras excepciones serán pasadas al llamador para que las maneje, y pueda posiblemente detener el programa principal. La variable local pos es asignada con el resultado de la función rindex. Esta función es también la razón para atrapar la excepción; de otra forma, el programa principal podría detenerse en el primer archivo encontrado sin '.' en él. Las variables locales pueden ser declaradas en cualquier lugar dentro de ocaml con 'let <variable> = <valor> in <código>'. Después de la finalización del código dado, la variable está fuera de ámbito y será olvidada. Los datos serán pasados al recolector de basura para ser eliminados de la memoria. Las llamadas a funciones normalmente utilizan paréntesis. La llamada a la función a'String.sub' recibe 3 parámetros: la cadena 'file', el entero '(pos+1)' y el entero '(String.length file-pos-1)'. El último parámetro llama a la función 'String.length' con un único parámetro 'file'. Entonces, las funciones están ansiosas por sus parámetros; los paréntesis son necesarios sólo cuando los parámetros son rellenados con cálculos.

También '(+)' y '(-)' son funciones del módulo pervasives. Es muy fácil definir tus propios operadores; sólo agregar paréntesis alrededor de su definición, y están listos.

If then else

La siguiente rutina 'filesize' en el código de ejemplo es mucho más larga, pero presenta en gran medida las sub-funciones y las sentencias 'if <bool-expr> then <expr> else <expr>'. Esta función crea una cadena a partir de un número int64 para que un humano pueda leer los tama�os de archivos y directorios. Los tipos de los parámetros no son dados normalmente; son determinados por ocaml basándose en su uso. Cuando algo no está claro, el compilador o interprete se quejará de eso antes de ejecutar el código.

let filesize s =
let tostr f =
  if f>9.9 then
    string_of_int (int_of_float (f +. 0.5))
  else
    let res = string_of_float (floor (f *. 10.0 +. 0.5) /. 10.0) in
    if String.length res=2 then
      res ^ "0"
    else 
      res
in
let bytes = Int64.to_float s in
if bytes > 512.0 then
  let kb = bytes /. 1024.0 in
  if kb > 512.0 then
    let mb = kb /. 1024.0 in
    if mb > 512.0 then
      let gb = mb /. 1024.0 in
      tostr gb ^ " Gb"
    else
      tostr mb ^ " Mb"
  else
    tostr kb ^ " kb"
else
  Int64.to_string s
;;

La librería estándar tiene una serie de funciones de conversión. Estas funciones normalmente siguen la forma de 'int_of_float' y 'string_of_float'. Los tipos específicos como 'Int64' usan notaciones abreviadas como 'Int64.to_float'. Las concatenaciones de cadenas se hacen con el operador '(^)'. Normalmente, las funciones son definidas para un tipo específico, por lo que hay nuevos juegos de funciones aritméticas para los float como '(+.)', '(*.)' and '(/.)'. La sub-función 'tostr' incluye algunos cálculos extras para cambiar algo de la forma '5. Gb' a una forma mas bonita como '5.0 Gb'.

Notación de listas y conversiones de tipos

La siguiente función, 'converttime', convierte una cadena a un float. OCaml usa floats para las fechas por 2 razones. La primera es para evitar posibles problemas de a�o 2000, y puede ser usado también para medidas de menos de un segundo. La función acepta acrónimos ingleses para los nombres de los meses. Entonces, presentemos la lista y el par para crear una traducción de acrónimos a números.

let month = [("jan", 0); ("feb", 1); ("mar", 2); ("apr", 3); ("may", 4); ("jun", 5);
             ("jul", 6); ("aug", 7); ("sep", 8); ("oct", 9); ("nov", 10); ("dec", 11)]
;;

Esta lista es totalmente estática, y puede ser usada por la función estándar List.assoc para convertir una cadena en el correspondiente número.

let converttime str =
try
begin match
  if str>"a" && str<"z" then
  ( int_of_string (string_sub str (String.rindex str ' '+1) 99),
    List.assoc (string_sub str 0 3) month,
    1
  )
  else
  ( int_of_string (string_sub str 0 (
      try String.index str '-' with Not_found -> 99
    )),
    ( try let pos=String.index str '-'+1 in
        int_of_string (string_sub str pos (
          try String.index_from str pos '-'-pos with err -> 99
        ))-1
        with err -> 0
    ),
    ( try let pos=String.index str '-'+1 in
        int_of_string (string_sub str (String.index_from str pos '-'+1) 99)
        with err -> 1
    )
  )
with (yr,mn,md) ->
(* print_string ("Last access before: "^
     string_of_int (if yr<50 then yr+2000 else if yr<100 then yr+1900 else yr)^"-"^
     string_of_int (mn+1)^"-"^
     string_of_int md^"\n"); 
*)
  fst (mktime 
  { tm_sec = 0; tm_min = 0; tm_hour = 0;
    tm_mday = md; tm_mon = mn;
    tm_year = if yr<50 then yr+100 else if yr<100 then yr else yr-1900;
    tm_wday = 0; tm_yday = 0; tm_isdst = false
  })
end with err ->
  print_string ("Cannot decipher this date string '" ^ str ^ "'\n"); max_float
;;

La nueva operación en esta función es 'match <expr> with <template> -> expr'. Esta es una de las instrucciones de ocaml más versátiles. Puede ser usada para examinar los contenidos de variables y obtener la información que se necesite de ellas. Esta función crea la tupla (a�o, mes, dia-del-mes) a partir de dos notaciones de fecha diferentes. Para depurar esta función se incluye la instrucción 'print_string' pero comentada para evitar llenar de información innecesaria la salida del programa. Normalmente hay algún mecanismo de log para hacer que los mensajes extra sean opcionales para el usuario. La instrucción 'print_string' muestra la notación ISO de la fecha dada; crea un a�o de 4 digitos y da un número de mes, tomando Enero como 1 en lugar del uso interno de Unix en el que Enero es el 0.

Eata función también muestra el uso de 'try <expr> with err -> <expr>' que atrapa toda posible excepción y completa la variable 'err' con los detalles de la excepción. Esta This función puede generar una buena cantidad de excepciones diferentes excepciones, y francamente no estoy muy interesado en los detalles. La rutina sólo se queja al usuario sobre la cadena de fecha dada y nada más. Devuelve el mayor float posible para incluir cualquier nombre de archivo.

La principal función estándar es 'Unix.mktime' . Espera recibir un registro lleno con números acerca de la hora actual. Esta función devuelve un par con el float necesario y un registro normalizado. Con la función de pervasives fst devuelve solamente el primer parámetro del par.

El ';' antes de 'max_float' indica que la expresión devuelve un float, pero las instrucciones anteriores al ';' son calculadas primero. Esta es la primera instrucción no-funcional dentro del código de ejemplo. OCaml no es estrictamente funcional, pero tiene toda la potencia de otros lenguajes funcionales.

Estructura de datos dinámica

Ahora es el momento de tratar con estructuras de datos reales que son construidas dinámicamente y que pueden ser usadas de muchas formas diferentes.

type entrytype =
| Dir of entry list   (* directory with a list of files *)
| File of string      (* a file inside a directory *)

and

entry = {
	mutable e_name: string;   (* name of a file or directory *)
	e_type: entrytype;        (* what type is this together with type
                                     related information *)
	e_atime: float;           (* last access time *)
	e_size: int64;            (* size of the file or size of all the matching
                                     files in the directory *)
}

La sentencia 'and' es usada para unir las dos definiciones. Son creadas al mismo tiempo por lo que 'entrytype' puede incluir 'entry' y viceversa. 'entrytype' puede consistir de cualquiera de estas 2 cosas: un directorio con una lista de entradas o un archivo con su tipo de archivo. La entrada de directorio tiene un nombre mutable. Esto puede ser utilizado más tarde para cambiar el nombre del archivo por la ruta completa del archivo.

Como con ANSI C, los operadores para álgebra Booleana son '(&&)' y '(||)'.

Recursividad

let rec dirwrite el depth sortfn =
List.iter (
  fun e ->
    match e.e_type with
    | Dir lst ->
       if e.e_size <> Int64.of_int 0 then begin
         print_string ((String.make (depth*2) ' ') ^ "Directory " ^
           e.e_name ^ " = (" ^ filesize e.e_size ^ ")\n");
         dirwrite lst (depth+1) sortfn
       end
    | File string ->
       print_string ((String.make (depth*2) ' ') ^ e.e_name ^
         " (" ^ filesize e.e_size ^ ")\n")
  ) (List.sort sortfn el)
;;

Aquí está la función recursiva ('rec') 'dirwrite' que recorre un árbol dado 'el' y que escribe el resultado a la salida estándar. El parámetro 'depth' indica la cantidad de espacios para escribir una estructura de árbol con los nombres de archivos. La función ordena todas las listas con la función dada 'sortfn'. La nueva estructura del lenguaje aquí es 'fun <parm-1> ... <parm-n> -> <expr>'. Esta construcción crea una función sin nombre. Los parámetros de esta construcción con forma de función pueden ser usados como un patrón para corresponder pares.

Esta función suprime los directorios que tienen 0 bytes de tamaño para reducir la salida inútil.

Variables vs. definiciones

(* List of global variables *)

let min_size = ref (Int64.of_int 0) and    (* minimum size of a file in bytes *)
    last_access = ref max_float and        (* last access time in seconds since 1970 *)
    has_type = ref "" and                  (* type of file to show or empty to
                                              show all *)
    name_match = ref "" and                (* regular expression to match the filename
                                              with; empty is show all *)
    name_regexp = ref (Str.regexp "") and  (* pre-calculated regular expression *)
    no_symlinks = ref false                (* don't follow symbolic links to
                                              directories *)
;;

Esta es una lista de variables que pueden ser cambiadas a causa de una construcción 'ref <expr>'. Normalmente, las definiciones son simplemente una etiqueta para sus contenidos. Estas definiciones son punteros a una dirección de memoria y pueden ser leidos con '!<variable>' y escritos con '<variable> := <expr>'. Los parámetros dados al programa pueden hacer cambios a la forma en que los archivos son leídos.

let rec dirread path =
let list = ref [] and
    size = ref (Int64.of_int 0) in
try
let dh = opendir path in
while true do
  let file = readdir dh in
  if file<>".." && file<>"." && file<>"CVS" && String.sub file 0 1 <> "." then
  let s=stat (path^"/"^file) in
  if s.st_kind = S_DIR &&
    (not !no_symlinks || (lstat (path^"/"^file)).st_kind <> S_LNK)
  then
    let dir = dirread (path^"/"^file) in
    list :=
    { e_name = file;
      e_type = Dir (fst dir);
      e_atime = s.st_atime;
      e_size = snd dir
    } :: !list;
    size := Int64.add !size (snd dir)
  else if
    (!has_type = "" || gettype file = !has_type) &&
    s.st_size > !min_size &&
    s.st_atime < !last_access &&
    (!name_match = "" || Str.string_match !name_regexp file 0)
  then begin
    list :=
    { e_name = file;
      e_type = File (gettype file);
      e_atime = s.st_atime;
      e_size = s.st_size;
    } :: !list;
    size := Int64.add !size s.st_size
  end
done;
(!list, !size)
with
| End_of_file -> (!list, !size)
| Unix_error (EACCES, err, parm) -> (!list, !size)
;;

Las siguientes funciones son introducidas en la función 'dirread':

  • Unix.opendir para comenzar a leer un directorio.
  • Unix.readdir para leer un nombre de archivo.
  • Unix.stat para un registro (Unix.stats) con estadísticas de un archivo.
  • Unix.lstat para estadísticas de un enlace.
  • Int64.add para sumar dos variables de tipo int64
  • Str.regexp para crear una nueva expresión regular interpretada
  • Str.string_match para corresponder una cadena con una expresión regular
  • Pervasives.(::) para crear una lista con un elemento extra delante del que ya existe
  • Pervasives.true como una constante Booleana
  • Pervasives.snd para devolver la segunda parte de un par
  • exception Unix.Unix_error (EACCESS, err, parm) que es lanzada cuando se encuentra un acceso denegado.
  • También hay una nueva contrucción 'while <boolean-expr> do <code> done' simplemente hace lo que se supone que debería hacer.

    Pequeño es hermoso

    let rec flat el path =
    List.fold_right (
      fun e ls ->
        match e.e_type with
        | Dir lst -> flat lst (path ^ "/" ^ e.e_name) @ ls
        | File string ->
            e.e_name <- (path ^ "/" ^ e.e_name);
            e :: ls
      ) el []
    ;;
    

    Esta prolija y pequeña rutina 'flat' aplana el árbol 'el'. Toma cada archivo de cada rama y crea una lista simple con todos los archivos encontrados. Esto es hecho con una de las rutinas estándar más versátiles dentro de ocaml: la rutina 'List.fold_right' . Esta rutina introduce una máquina de estado (scarab) que recorre la lista y opera en cada elemento encontrado. Produce una nueva estructura (droppings) como resultado -- en este caso, una lista plana.

    La construcción '<record-field> <- <expr>' cambia los contenidos de un campo de un registro mutable. Sin los campos mutables, se puede mutar registros sólo creando uno nuevo con montones de campos heredados del antiguo. Utilizar esta notación es un atajo para eso.

    let name_order a b =
    compare a.e_name b.e_name
    ;;
    
    let type_order a b =
    let typea = match a.e_type with Dir ls -> "dir" | File tp -> tp and
        typeb = match b.e_type with Dir ls -> "dir" | File tp -> tp in
    if compare typea typeb = 0 then
      compare a.e_name b.e_name
    else compare typea typeb
    ;;
    
    let atime_order a b =
    compare a.e_atime b.e_atime
    ;;
    

    Un juego de funciones de ordenamiento para usar dentro de 'dirwrite'. La función 'compare' devuelve los ampliamente usados valores -1 para menor que, 0 para igual y +1 para mayor que.

    Parámetros de línea de comandos

    let dir = ref "." and
        sort = ref name_order and
        show = ref 0
        in
    
    Arg.parse [
       ("-t",Arg.Unit (fun () -> sort := type_order),
         "Sort by type and filename");
       ("-l",Arg.Unit (fun () -> sort := atime_order),
         "Sort by last access time");
       ("-n",Arg.Unit (fun () -> show := 1),
         "List filenames");
       ("-b",Arg.Unit (fun () -> show := 2),
         "List both filenames and sizes");
       ("-s",Arg.Unit (fun () -> no_symlinks := true),
         "Don't follow symbolic links");
       ("--before",Arg.String (fun s -> last_access := converttime s),
         "Last access older than give date (format 'yyyy-mm-dd' or 'mmm yyyy')");
       ("--size",Arg.Int (fun i ->
            min_size := Int64.mul (Int64.of_int i) (Int64.of_int (1024*1024))
         ), "File size bigger than size in Mbytes");
       ("--type",Arg.String (fun s -> has_type := s),
         "File is specific type");
       ("--name",Arg.String (fun s ->
            name_match := s; name_regexp := Str.regexp (s ^ "$")
         ), "Filename matches regular expression")
    ] (fun d -> dir := d) "show [DIR]";
    let res = dirread !dir in
    if !show=0 then begin
      dirwrite (fst res) 0 !sort;
      print_string ("Total size " ^ filesize (snd res) ^ "\n")
    end else
      List.iter
        (fun e ->
          print_endline (e.e_name ^ if !show=2 then " ("^filesize e.e_size^")" else "")
        ) (List.sort !sort (flat (fst res) !dir))
    ;;
    

    Y aquí está la rutina principal. Llama a la rutina Arg.parse para separar los parámetros dados al programa. Pero esto es demasiado no-GNU para mí. Escribí mi propia versión de ésta que sigue los estándares de codificación de GNU un poco más que la que viene por defecto (Gnuarg). Como esa otra versión es un poco más complicada entonces sólo incluiré los fuentes que la usan.

    Generando binarios

    El código puede ser obtenido de aquí. Solamente desempaquételo en algún lugar con 'tar -xzf show.tar.gz' y vaya al directorio de las fuentes con 'cd show/src'. Hay también un Makefile que compila a código máquina y que instala todo. Pero los Makefiles son demasiado "duros" para mostrarlos en este artículo. Los detalles están ahí en las fuentes. La forma general de compilación es.

    ocamlopt -o show unix.cmxa str.cmxa basics.cmx show.ml
    

    Las únicas librerías no-estándar en uso aquí son unix.cmxa y str.cmxa.

    make
    su
    make install
    exit
    show --help
    show -s ~ --size 3 --before "apr 2003"
    

    Esto concluye con el programa de ejemplo.

    Características del lenguaje

    Recolector de basura
    Simplemente olvídese de las variables que contienen estructuras de datos completas. Una vez que salen de su ámbito, la estructura completa será eliminada de la memoria en nada de tiempo.
    Estructuras de datos flexibles
    Cualesquiera 2 estructuras de datos pueden ser combinadas sin complicación. Simplemente cree un array de registros que contenga 2 campos con tablas de hash de cadenas. No hay problema ahi... todo dentro de una sola variable puede ser pasado a las funciones o puede ser utilizado globalmente en el programa.
    No se necesitan punteros
    Una variable puede tener cualquier tipo y cuando una nueva variable se crea
    Chequeos de límites flexibles y en el lenguaje
    El lenguaje puede chequear los límites de los arrays y las cadenas automáticamente, o dichos chequeos pueden ser deshabilitados para tener un impulso de velocidad extra. Sin ello, el lenguaje puede dar un error de segmentación, pero esa es la elección del programador.
    Manejo de errores de alta calidad
    Totalmente integrado en el lenguaje y sin impacto de rendimiento evidenciable.
    Generador de código nativo e intérprete de código
    Todas las herramientas están ahí -- intérprete (ocaml), byte code (ocamlc) y compilador de código nativo (ocamlopt) -- todos los deseos son concedidos. El paquete también viene con un generador de documentación (ocamldoc) y un profiler simple de usar (ocamlprof) que le agrega conteos de uso como comentarios al código fuente original. El lenguaje es también compatible con los profilers más soficticados que hay.
    Capa de compatibilidad con ANSI-C
    Es posible incluír rutinas ANSI C dentro de programs OCaml, y rutinas OCaml dentro de programas C. Esto tiene una API muy fácil de usar. Un poco menos fácil es la creación de estructuras de datos OCaml dentro de C; para mi, ése fuel el origen de muchos fallos de segmentación. Entonces, mis rutinas llaman a rutinas exportadas OCaml para rellenar las estructuras de datos y así crear solamente cadenas y números de OCaml en C. De esa manera no tendría que lidiar con depurar el código C... OCaml es mucho más fácil de depurar para mi.
    Orientación a objetos
    No es mi paradigma de programación favorito, pero es posible construir programas orientados a objetos en este lenguaje. Dichas características no son parte de este artículo. Puedo vivir sin ellas.
    Una lista de correo activa
    Esta lista está en caml-list@inria.fr y es normalmente en Inglés. Si, este proyecto originalmente francés se ha tomado el trabajo de traducir casi todo lo que tenían. Esto no fué tarea fácil para ellos, así que estemos agradecidos.

    Contras:

    Duplicar esfuerzos en las librerías
    Hay librerías separadas para distintos tipos de arrays grandes, archivos grandes, y enteros extra largos. Esto no es un gran problema, porque siempre se puede empezar con las estructuras normales y usar la librería especial cuando se haga necesario. Los nombres de las distintas funciones están bastante estandardizados, así que renombrar las llamadas a las funciones no es muy necesario. Los enteros extra largos sin embargo son muy diferentes de los enteros normales. Esa parte de las funciones estándar realmente necesita un poco de ajuste.
    Legibilidad
    Se necesita familiarizarse con las construcciones básicas del lenguaje, para que el código real tenga algo de sentido. Algunas construcciones pueden parecer realmente extrañas sin un conocimiento íntimo del lenguaje. OCaml no es un lenguaje muy natural y tiene una notación muy poderosa y corta para las cosas. Pero esto no es mucho peor que los lenguajes como el ANSI C, Perl, o lisp.
    No es suficientemente conocido en el mundo de Linux
    Este lenguaje tiene interfaces excelentes para librerías estándar y fáciles enlaces con ANSI C, pero aún no es muy conocido. Quisiera crear varios artículos como éste para cambiar eso un poco. Este es un lenguaje realmente genial para programar, y te da poder real sin las desventajas comunes en otros lenguajes. Los programadores deberían probarlo y sentir ese poder una vez al menos.

     

    [BIO] Desarrollador en una pequeña firna de tecnología en Holanda llamada V&S bv. (www.v-s.nl) Vendemos firewalls, anti-virus y cajas anti spam basadas en Linux. Usando más y más el lenguaje OCaml para escribir mis aplicaciones. Ocupado escribiendo un servidor http liviano con un lenguaje interno de script (camlserv.sourceforge.net, código fuente aquí) Interesado en escribir sobre juegos basados en IA. Siempre intentando escribir uno, nada listo aún.

    Copyright © 2004, Jurjen Stellingwerff. Licencia de copia http://linuxgazette.net/copying.html

    Publicado en el número 99 de Linux Gazette, Febrero 2004