viernes, agosto 01, 2008

Dejando trabajo para después

Hace poco tuve un pequeño problema con el proyecto que estoy realizando. Se trataba de lo siguiente.

Existía una tarea que debía realizarse en forma transaccional (agregar unos 4 o 5 registros en una base de datos que estaban relacionados) y después era necesario mover unos archivos a la posición indicada. El path de estos archivos dependía del campo id de la base de datos y este solo se conocería cuando se completara toda la transacción. (ejecución de SubmitChanges() )

En general, el "patrón" de problema es el siguiente:

Existen t1, t2 ... tn tareas. Estas tareas necesitan realizar trabajo adicional pero debe realizarse después de un evento.

Siendo asi, se me ocurrió esta idea. Tipar estas tareas que necesitan trabajo extra como:


    1 unit -> unit -> unit


En cristiano... cuando se ejecuta la tarea devuelve una función que requiere ser ejecutada.

El ejemplo de coquito que hice para el blog es el siguiente:


    6 let location = ref "workplace"

    7 

    8 let dayWork s () =

    9     printfn "Work #%A in %s" s !location

   10     fun () -> printfn "Home work #%A in %s" s !location

   11 

   12 let works  = [for i in 1..5 -> dayWork i]



Existen 5 trabajos del día. Cada trabajo del día conlleva trabajo que queda de tarea para la casa. Pero para realizar este trabajo resulta necesario primero "ir a la casa"

Este evento global lo represento con la celda de memoria "location". En el caso que mencione anteriormente el evento que se requiere para terminar es un SubmitChanges()
(ir a casa es básicamente ésta función)


   19 let goHome () = location := "home"



Ahora bien... la idea es implementar un mecanismo que ejecute las tareas, luego el evento y luego todo el trabajo de las tareas ejecutadas que requería del evento.

Al principio pensé en implementar una "computation expression" pero es mas fácil de lo que imagine... el viejo fold_left

    4 let nop = (fun () -> ())



   14 let finish after this  =

   15     let afterWork =

   16         List.fold_left (fun x y -> x >> y() ) nop after

   17     this()

   18     afterWork()



Lo que estamos diciendo con el fold es lo siguiente.

Tenemos una operación nop. Ejecute la primera tarea y luego componga el trabajo por hacer con el nop anterior. y haga esto para toda la lista de tareas. al final el fold habrá ejecutado todas las tareas y habrá acumulado una funcion con todo el trabajo por hacer.

Ejecutamos el evento necesario. Y ejecutamos lo que requería de este evento (El resultado del fold).

Aprovechándose uno de la sintaxis puede uno hacer esto:

   22 finish

   23     [dayWork 1

   24     dayWork 2

   25     dayWork 3]

   26     goHome



Los punto y comas no son necesarios en listas que se escriben en varias líneas. Esto le da un "look imperativo" que me agrada :P.

Él resultado:

Work #1 in workplace
Work #2 in workplace
Work #3 in workplace
Home work #1 in home
Home work #2 in home
Home work #3 in home