La entrada anterior, me ha dado la idea de crear una pequeño módulo para el manejo de tiempos (es decir, horas, minutos, segundos etc...), esto me puede permitir entender algo mejor la parte de módulos de OCaml y seguir cogiendo soltura.
Primero voy a intentar crear un código que implemente el algebra de sumar tiempos en el sistema usual días, horas, minutos, segundos.
Definir un tipo para tiempo
Queremos describir un tipo que condense una variable tiempo compuesta de días, horas, minutos y segundos, y realizar operaciones (sumar, restar, crear listas) basándome en este tipo.
Se me ocurren dos definiciones:
type tiempo = { d: int; h: int; m:int; s:int}
type tiempo = DHMS of int * int * int * int
Vamos a implementar la primera opción (más adelante puede ser útil pensar en funciones construidas sobre el tipo option aplicado a este tipo pero primero vamos a comenzar sin estas consideraciones).
Álgebra para tipo tiempo
Hay varias posibilidades para hacer esto, una sencilla es considerar que la unidad base son segundos, y definir dos funciones de manera que podamos hacer la operaciones en segundos y luego volver a nuestro tipo tiempo:
let from_int (x : int) : tiempo = ...
let to_int (x : tiempo) : int = ...
Veamos unas definiciones concretas para estas funciones:
let to_int (x : tiempo) : int = x.s+x.m*60+x.h*60*60+x.d*60*60*24
let from_int (x : int) : tiempo =
let ss = Int.rem x 60 in
let mm = Int.rem (x-ss) 3600 in
let hh = Int.rem (x-s-m*60) 86400 in
{ d=(x-s-m*60-h*3600)/86400 ; h = hh ; m=mm ; s=ss};
La función to_int
es muy directa y no presenta problemas. Sin embargo, esta definición de from_int
no es totalmente robusta, porque solo funciona si tenemos tiempos introducidos correctamente. Sin embargo, imaginemos que introducimos segundos o minutos mayores de 60, esto produce valores sin sentido. Una versión mejorada es:
let from_int_ok (x : int) : tiempo =
let ss = Int.rem x 60 in
let mm = (Int.rem (x-ss) 3600) / 60 in
let hh = (Int.rem (x-ss-mm*60) 86400) / 3600 in
{ d=(x-ss-mm*60-hh*3600)/86400 ; h = hh ; m=mm ; s=ss};;
De hecho si imaginamos que tiempo1
es un tiempo introducido incorrectamente tenemos que:
let ok_tiempo x = from_int_ok (to_int x) ;;
let tiempo1 = {d=1; h=5; m=70; s=91} ;;
let tiempo2 = ok_tiempo tiempo1;;
Ahora tiempo2
tiene un formato correcto o estándar. Lo podemos comprobar con este código donde hemos incluido ambas funciones y una función print_tiempo
:
open Format
type tiempo = { d: int; h: int; m:int; s:int}
let to_int (x : tiempo) : int = x.s+x.m*60+x.h*60*60+x.d*60*60*24
let from_int (x : int) : tiempo =
let ss = Int.rem x 60 in
let mm = Int.rem (x-ss) 3600 in
let hh = Int.rem (x-ss-mm*60) 86400 in
{ d=(x-ss-mm*60-hh*3600)/86400 ; h = hh ; m=mm ; s=ss};;
let from_int_ok (x : int) : tiempo =
let ss = Int.rem x 60 in
let mm = (Int.rem (x-ss) 3600) / 60 in
let hh = (Int.rem (x-ss-mm*60) 86400) / 3600 in
{ d=(x-ss-mm*60-hh*3600)/86400 ; h = hh ; m=mm ; s=ss};;
let ok_tiempo x = from_int_ok (to_int x)
let print_tiempo x = printf "%dD:%dH:%dM:%dS \n" x.d x.h x.m x.s
let t1 = { d=1 ; h=2 ; m=40 ; s=51 }
let t2 = { d=1 ; h=2 ; m=61 ; s=71 }
let t3 = ok_tiempo t2
let t4 = ok_tiempo t1
let () = printf "Introducimos d=1; h=2; m=40; s=51 \n";;
printf "Tiempo como entero %d \n" (to_int t1) ;;
printf "Tiempo como tiempo : \n";;
print_tiempo t1;;
printf "Introducimos d=1; h=2; m=61; s=71 \n";;
printf "Tiempo como entero %d \n" (to_int t2) ;;
printf "Tiempo como tiempo : \n";;
print_tiempo t2;;
printf "Tiempo como tiempo de %d \n" (to_int t2) ;;
printf "... usando from_int : " ;;
print_tiempo (from_int (to_int t2));;
printf "... usando from_int_ok : " ;;
print_tiempo (from_int_ok (to_int t2));;
printf "------------------------- \n";;
print_tiempo t3 ;;
print_tiempo t4 ;;
Operaciones
Recordemos que nuestro objetivo es realizar operaciones como sumar tiempos. Pero como hemos comentado ahora es directo, basta pasar a enteros, sumarlos y volver a tiempos.
(* Podemos definir un operador infix para sumar tiempos que se usa: let z = x +.+ y *)
let (+.+) (x: tiempo) (y : tiempo) : tiempo = from_int ( (to_int x) + (to_int y) )
Programa final
El programa final podría ser:
open Format
type tiempo = { d: int; h: int; m:int; s:int}
let to_int (x : tiempo) : int = x.s+x.m*60+x.h*60*60+x.d*60*60*24
let from_int (x : int) : tiempo =
let ss = Int.rem x 60 in
let mm = Int.rem (x-ss) 3600 in
let hh = Int.rem (x-ss-mm*60) 86400 in
{ d=(x-ss-mm*60-hh*3600)/86400 ; h = hh ; m=mm ; s=ss};;
let from_int_ok (x : int) : tiempo =
let ss = Int.rem x 60 in
let mm = (Int.rem (x-ss) 3600) / 60 in
let hh = (Int.rem (x-ss-mm*60) 86400) / 3600 in
{ d=(x-ss-mm*60-hh*3600)/86400 ; h = hh ; m=mm ; s=ss};;
let ok_tiempo x = from_int_ok (to_int x)
let (+.+) (x: tiempo) (y : tiempo) : tiempo =
from_int_ok ( (to_int x) + (to_int y) )
let print_tiempo x = printf "%dD:%dH:%dM:%dS \n" x.d x.h x.m x.s
let t1 = { d=1 ; h=2 ; m=40 ; s=51 }
let t2 = { d=1 ; h=2 ; m=61 ; s=71 }
let t3 = ok_tiempo t2
let t4 = ok_tiempo t1
let () = printf "Introducimos d=1; h=2; m=40; s=51 \n";;
printf "Tiempo como entero %d \n" (to_int t1) ;;
printf "Tiempo como tiempo : \n";;
print_tiempo t1;;
printf "Introducimos d=1; h=2; m=61; s=71 \n";;
printf "Tiempo como entero %d \n" (to_int t2) ;;
printf "Tiempo como tiempo : \n";;
print_tiempo t2;;
printf "Tiempo como tiempo de %d \n" (to_int t2) ;;
printf "... usando from_int : " ;;
print_tiempo (from_int (to_int t2));;
printf "... usando from_int_ok : " ;;
print_tiempo (from_int_ok (to_int t2));;
printf "------------------------- \n";;
print_tiempo t3 ;;
print_tiempo t4 ;;
printf "-- Sumando los dos tiempos anteriores: \n";;
print_tiempo (t4 +.+ t3);;
Creando un módulo
El siguiente paso que queríamos completar es como usar estas funciones dentro de un módulo, lo que nos permite agrupar y estructurar mejor nuestros programas. Un primer método es crear un archivo Tiempo.ml con el contenido de lo que queremos que sea nuestro módulo y separarlo del resto del código. Tendríamos,
type tiempo = { d: int; h: int; m:int; s:int }
let to_int (x : tiempo ) : int = x.s+x.m*60+x.h*60*60+x.d*60*60*24
let from_int (x : int ) : tiempo =
let ss = Int.rem x 60 in
let mm = Int.rem (x-ss) 3600 in
let hh = Int.rem (x-ss-mm*60) 86400 in
{ d=(x-ss-mm*60-hh*3600)/86400 ; h = hh ; m=mm ; s=ss};;
let from_int_ok (x : int) : tiempo =
let ss = Int.rem x 60 in
let mm = (Int.rem (x-ss) 3600) / 60 in
let hh = (Int.rem (x-ss-mm*60) 86400) / 3600 in
{ d=(x-ss-mm*60-hh*3600)/86400 ; h = hh ; m=mm ; s=ss};;
let ok_tiempo x = from_int_ok (to_int x)
let (+.+) (x: tiempo) (y : tiempo) : tiempo =
from_int_ok ( (to_int x) + (to_int y) )
let print_tiempo x = Format.printf "%dD:%dH:%dM:%dS \n" x.d x.h x.m x.s
Como vemos no he introducido cambios excepto printf
que lo he escrito como Format.printf
para evitar la necesidad de usar open Format. En el caso del resto del programa lo incluyo en un archivo ocaml3_module.ml donde las funciones del módulo ahora tienen nombres Tiempo.XYZ
. Dos comentarios:
a. Al definir los tiempos t1 y t2 he tenido que incluir en alguno de los elementos también el modulo (da igual que elemento: d, h, m, s) para que el compilador entienda como queremos definir el tipo record. Imagino que hay mejores formas y más idiomáticas de resolver esto.
b. Cuando tenemos un operador inflix en el módulo tenemos que indicarlo como: Tiempo.(x +.+ y)
ya que no hemos incluido un open Tiempo
.
open Format
let t1 = { Tiempo.d=1 ; h=2 ; m=40 ; s=51 }
let t2 = { Tiempo.d=1 ; h=2 ; m=61 ; s=71 }
let t3 = Tiempo.ok_tiempo t2
let t4 = Tiempo.ok_tiempo t1
let () = printf "Introducimos d=1; h=2; m=40; s=51 \n";;
printf "Tiempo como entero %d \n" (Tiempo.to_int t1) ;;
printf "Tiempo como tiempo : \n";;
Tiempo.print_tiempo t1;;
printf "Introducimos d=1; h=2; m=61; s=71 \n";;
printf "Tiempo como entero %d \n" (Tiempo.to_int t2) ;;
printf "Tiempo como tiempo : \n";;
Tiempo.print_tiempo t2;;
printf "Tiempo como tiempo de %d \n" (Tiempo.to_int t2) ;;
printf "... usando from_int : " ;;
Tiempo.print_tiempo (Tiempo.from_int (Tiempo.to_int t2));;
printf "... usando from_int_ok : " ;;
Tiempo.print_tiempo (Tiempo.from_int_ok (Tiempo.to_int t2));;
printf "------------------------- \n";;
Tiempo.print_tiempo t3 ;;
Tiempo.print_tiempo t4 ;;
printf "-- Sumando los dos tiempos anteriores: \n";;
Tiempo.print_tiempo (Tiempo.(t4 +.+ t3));;
En este caso la compilación se realizará (el orden de los archivos .ml es importante):
ocamlopt -o test.exe Tiempo.ml ocaml3_module.ml
Esta ha sido la tercera sesión en OCaml. Hemos definido un tipo record, y creado funciones usándolo, incluyendo un operador inflix. Finalmente lo hemos compilado separando una parte para usarla como módulo. Este último paso puede hacerse de diferentes modos hemos elegido el más sencillo (otros los intentare más adelante)