Ha llegado el momento de intentar usar dune. Lo primero es verificar que esta instalado, por ejemplo, usando opam.
> opam list -i | grep dune
Sino esta instalado dune podemos hacer
> opam install dune
Creando un projecto
En OCaml dune es una herramienta para crear librerías y aplicaciones de modo sistemático e integrado con opam. Para crear un proyecto llamado ocaml4 utilizamos la opción init:
> dune init project ocaml4
lo que va a crear un directorio llamado ocaml4 y la estructura de archivos
Archivo | Tipo | Función |
---|---|---|
bin | directorio | código y reglas para crear un ejecutable |
_build | directorio | aquí se crearan ejecutables y archivos intermedios |
dune-project | archivo | archivo con sintaxis tipo Lisp con información del projecto |
lib | directorio | código y reglas para crear una librería |
ocaml4.opam | archivo | archivo que se completara con info en dune-project para opam |
test | directorio | código y reglas para realizar tests |
si miramos en bin
hay un archivo main.ml
con el típico hello world. Podemos incluir información en dune-project del tipo autores, licencias o descripción, pero no es necesario para las siguientes operaciones.
Creando y usando un executable
> dune build
Esto crea un ejecutable en _build/install/default/bin/main.exe
que si ejecutamos
./_build/install/default/bin/main.exe
nos da como salida:
Hello, World!
ya que el código en bin/main.ml
es
let () = print_endline "Hello, World!"
Este ejecutable se ha creado cuando hemos usado dune build
con información en bin/dune
Archivo | Tipo | Función |
---|---|---|
bin/main.ml | source ocaml | código |
bin/dune | archivo texto | archivo con sintaxis tipo Lisp con reglas para compilar |
Compilando con Owl
En esta cuarta sesión de OCaml voy a usar la librería científica llamada Owl,
let x = Owl.Mat.of_array [|0.0;1.0;2.0;3.0|] 2 2
let () = print_endline "Hello, World!"
Si cambiamos main.ml por el código anterior da un error bin/main.ml
, que es Error: Unbound module Owl, por tanto hemos de incluirlo en el archivo dune
(executable
(public_name ocaml4)
(name main)
(libraries ocaml4 owl))
el nombre es owl y no Owl, esto lo podemos ver usando ocamlfind list | grep owl
. Si intentamos compilar con dune build
tenemos otro error Error (warning 32 [unused-value-declaration]): unused value x., que sin embargo no aparecería si compilara como hemos visto en sesiones previas usando las herramientas manuales (ocamlfind con ocamlopt/ocamlc). Esto se debe a que por defecto dune utiliza flags para el compilado que dan como errores y no warnings determinadas prácticas como no usar una variable definida. En la siguiente porción de código incluimos lo siguiente
(executable
(public_name ocaml4)
(name main)
(libraries ocaml4 owl))
(flags (:standard -warn-error "-unused-value-declaration")))
Ahora la compilación con dune build
da un warning (y no un error) Warning 32 [unused-value-declaration]: unused value x.
Aprendiendo Owl
Ahora voy a incluir código usando Owl sin ningún objetivo específico, únicamente para aprender a usar algunas funciones básicas.
open Owl
let x = Owl.Mat.of_array [|0.0;1.0;2.0;3.0|] 2 2
let x1 = Owl.Mat.uniform 2 2
let y = Owl.Mat.of_array [|0.;1.;2.;3.|] 4 1
let z = Owl.Mat.of_array [|0.;1.;2.;3.|] 1 4
let x2 = Mat.(x + x1)
let x3 = Mat.(x *@ x1)
let x4 = Mat.(y *@ z)
let x5 = Mat.(z *@ y)
let () = print_endline "Aprendiendo OWL & OCAML...";
print_endline "x :"; flush_all() ;
Mat.(x |> print) ; flush_all() ;
print_endline "y :";
Mat.(y |> print) ; flush_all();
print_endline "z :";
Mat.(z |> print) ; flush_all();
Mat.(x1 |> print) ; flush_all();
Mat.(x2 |> print) ; flush_all();
Mat.(x3 |> print) ; flush_all();
Mat.(x4 |> print) ; flush_all();
Mat.(x5 |> print) ; flush_all();
En el código previo hemos usado open Owl, por tanto en let x2 = Mat.(x + 1)
es posible, en todo caso como estamos compilando con la librería owl, podemos usarla sin open pero esto implica la sintaxis let x2 = Owl.Mat.(x + 1)
.
Ejercicio: Cambio de base
Más que empezar a revisar una a una las funciones disponibles, voy a intentar como siempre, hacer un ejercicio. En este caso voy a tener un conjunto de vectores en una base concreta, y voy a calcular la matriz de cambio de base a otra base diferente y la forma que adquieren los vectores en la segunda base.
Lo primero es recordar algo de álgebra lineal. Una de las estructuras más importantes es un espacio vectorial, que esencialmente son objetos que podemos sumar y escalar. Los números reales son un caso muy simple ya que cualquier suma de números reales es un número real, y multiplicar un número real por otro es también un número real. Si tomamos el producto cartesiano $\ℝ^2=\ℝ$x$\ℝ$, sus elementos son pares de la forma $(a,b)$ con $a,b\∈\ℝ$. Podemos ver que en $\ℝ^2$ tenemos una suma y un producto por un escalar:
- $(a,b)+(c,d) = (a+c,b+c)$
- $\α (a,b)$ = $(\αa, \αb)$
Estas operaciones cumplen los requisitos para tener un espacio vectorial, y la dimensión de este espacio vectorial es 2, y podemos escribir cada elemento $x\∈\ℝ^2$ como $x=\α e_1 + \β e_2$, donde $e_1,e_2\∈\ℝ^2$ y son ortogonales. El conjunto ${e_1,e_2}$ es una base del espacio vectorial. Veamos un ejemplo:
Si $x=(4,3)$ y $e_1 = (1,0)$ and $e_2=(0,1)$ entonces $x=4 e_1 + 3 e_2$. Bien, también podemos observar que si tomamos como base $v_1 = (4,0)$ and $v_2=(0,-3)$, ahora mi vector es $x= v_1 + (-1)v_2$. Una vez tenemos una base podemos describir cada vector por sus coordenadas que serian los factores $\α,\β$ en $x=\α e_1 + \β e_2$, pero estas coordenadas dependen como vemos de como elegimos nuestra base. El ejercicio es obtener una matriz que dadas dos base concretas me permita transformar las coordenadas en una base en las coordenadas en otra base.
El algoritmo es sencillo, únicamente tenemos que escribir los vectores de una base en los vectores de la otra base, veamos: $x= v_1 + (-1.0) v_2$, y observamos que en este caso es sencillo: $v_1 = 4 e_1$ y $v_2=-3 e_2$, y $x= v_1 + (-1.0) v_2$ y por tanto $x= 4 e_1 + (-1.0)(-3.0) e_2$, como esperábamos. Vamos a hacerlo en OCaml
let e1 = [| 1.0; 0.0 |]
let e2 = [| 0.0; 1.0 |]
let v1 = [| 4.0; 0.0 |]
let v2 = [| 0.0; -3.0 |]
(* Ponemos las coordenadas de v1 y v2 en la base (e1, e2) en
forma de matriz, debemos ver que las columnas de la matriz
resultante son las coordenadas de ambos vectores. Lo llamamos
ev ya que dadas las coordenadas en base v={v1,v2} me da las
coordenadas en e = {e1,e2} *)
let ev = Owl.Mat.of_array [| 4.0; 0.0; 0.0; -3.0 |] 2 2
(* Creamos un vector columna que tenga las coordenadas de x
en la base {v1, v2}. xv son las coordenadas de x en base
{v1, v2} como vector columna *)
let xv = Owl.Mat.of_array [| 1.0; -1.0 |] 2 1
(* Multiplicamos ev y xv, producto de matrices y debemos
obtener un vector columna con las coordenadas de x
en la base {e1, e2} *)
let c = Owl.Mat.(ev *@ xv)
let () = print_endline "Aprendiendo OWL & OCAML...";
Owl.Mat.(ev |> print) ; flush_all() ;
Owl.Mat.(xv |> print) ; flush_all() ;
Owl.Mat.(c |> print) ; flush_all() ;
La salida es
Aprendiendo OWL & OCAML...
C0 C1
R0 4 0
R1 0 -3
C0
R0 1
R1 -1
C0
R0 4
R1 3
Esto es ilustrativo pero no muy útil ya que tenemos que introducir manualmente los elementos de la matriz de cambio de base, y ha sido fácil por la simplificación introducida al tener una base muy sencilla ${e_1, e_2}$. Una manera de automatizarlo, es referir todo a esta base que llamaremos base canónica de modo que un cambio de base arbitrario puede determinarse como el cambio de base inicial a la canónica y de esta a la base final. Lo dejo como ejercicio que resolveré en la siguiente sesión.