Mostrando entradas con la etiqueta programacion. Mostrar todas las entradas
Mostrando entradas con la etiqueta programacion. Mostrar todas las entradas

12 de marzo de 2009

Coloreado de cambios con Emacs

Hace mucho que no escribo, hoy voy a hablar un poco del Maravilloso Emacs. Editor que uso para editar cualquier fichero de texto desde hace poco más de un año. Y que me encanta. Y como lo hecho de menos cuando tengo que usar el Horroroso Vim.

En concreto, quiero comentar una pequeña funcionalidad de Emacs. Como colorear las partes nuevas en el fichero que estés escribiendo en un momento dado. Bueno, a partir de aquí, los párrafos entre paréntesis (lo que otros llaman código Lisp) va en el fichero de configuración de Emacs.

Primero creamos una función que nos limpia el coloreado de los cambios.

(defun clear-highlight ()
(interactive)
(if (boundp 'highlight-changes-mode)
(highlight-changes-remove-highlight (point-min) (point-max))))
Luego añadimos un par de accesos de teclado.
(define-key global-map (read-kbd-macro "<f3>") 'highlight-changes-mode)
(define-key global-map (read-kbd-macro "<f4>") 'clear-highlight)
Y ya esta. Ahora cuando apretemos por primera vez F3 con un fichero abierto, se activa la funcionalidad. Lo que escribamos a partir de entonces se colorea diferente. Si volvemos a apretar F3, se quita el coloreado, ..etc. Y con F4 lo que conseguimos es limpiar el coloreado como si empezáramos a editar de nuevo el fichero (ojo, no quiere decir que deshaga los cambios)

Aunque si quieres que te active el coloreado de cambios nada más abrir el fichero, puedes hacerlo con una linea como la que sigue. En este caso activa el coloreado para ficheros de LaTex.

(add-hook 'latex-mode-hook 'highlight-changes-mode)

Y si se quiere que se limpie el coloreado actual automáticamente cuando salvamos, bastaría con añadir una llamada a nuestra función clear-highlight cada vez que se salva.

(add-hook 'after-save-hook 'clear-highlight)

1 de septiembre de 2008

Panel de Televisiones con PS3 (II)

Otras entradas acerca del tema:
Hoy toca habla del software desarrollado para el panel de televisiones. El objetivo del software es dividir una imagen de gran resolución en 9 trozos, y mostrar cada trozo en una pantalla (a ser posible en orden para obtener una imagen global)

Primer Intento: MPI
El estándar MPI (Message Passing Interface) es una especificación de librería para facilitar la comunicación entre diferentes procesos. En el programa MPI se usa para enviar la imagen troceada desde una de las consolas al resto. Como primer paso, lo que se hizo fue instalar una implementación de MPI en todas las consolas (OpenMPI). También opte por Imagick++ como librería para leer las imágenes del disco duro. Y para mostrar las imágenes por pantalla me decido por SDL, que va de maravilla. Como control de versiones, darcs.

El programa final, era bastante fácil de manejar. Cogía todos los ficheros de imagen del directorio donde lo lanzas, y las va mostrando secuencialmente en las pantallas.

El problema de esta primera versión del programa. Terriblemente lento, tarda varios minutos en leer una imagen y transmitirla a las 9 PS3.

Segundo Intento: UDP
Viendo lo lento que va, le echo la culpa a dos cosas: el disco duro de la PS3 y el uso de MPI. Mal por mi parte, debí medir donde tardaba más el programa antes de ponerme a buscar soluciones, en fin. Me decido por atacar MPI.

La solución más obvia es sustituir MPI por una librería de más bajo nivel. Así que me pongo a cambiar las comunicaciones a UDP/IP. Lo primero, creo una aplicación simple, que escucha en un puerto UDP conocido. Los mensajes que recibe los interpreta como lineas y las dibuja por pantalla.

Y la aplicación principal, solo tiene que leer del disco duro y mandar la imagen troceada. Se elimina la parte de SDL de esta aplicación principal, y de las aplicaciones clientes se elimina la librería Imagick++. Todo resulta más elegante y más compacto.

Resultados, una aplicación ligeramente más lenta que la versión MPI. Todo un éxito :-D.

Al final, aunque es un rotundo fracaso en cuanto a optimizar la aplicación, me quedo con la versión UDP, ya que nos resulta más fácil de arrancar el panel de televisiones poniendo el cliente en el inicio de sesión. Y el servidor de imágenes tanto lo podemos tener en una consola, como en un ordenador externo conectado al switch. Incluso nos permite hacer virguerías como tener varios servidores de imágenes apuntando a diferentes televisores.

Tercer Intento: Tan rápido que falla
Se hace varios intentos a la desesperada para intentar mejorar los tiempos. Se trata de paralelizar el código usando hilos, no ganamos nada. Se trata de usar los coprocesadores extra (SPU), un quebradero de cabeza y se nos hecha el tiempo encima. Al menos se soluciona un problema de la aplicación, que al cabo de un par de horas se quedaba colgada. Se arregla reservando memoria al principio de la ejecución y rehusando los buffers.

Al final un cambio mínimo nos salva. La lectura de la imagen se hace con Imagick++, que una vez leído del disco, se va dividiendo en los diferentes buffers que se mandan por UDP, leyendo los pixels de la imagen. Veo en la documentación que existe una función que rellena el buffer, Magick::Image::write(...). La pruebo, por quitar un bucle del código, y ¡presto!. El programa se acelera desde los minutos por imagen a milisegundos por imagen.

Tan rápido llega a ir el programa, que los clientes UDP pierden más de la mitad de los mensajes, las imágenes se ven unas encima de otras. Un follón. Y como el tiempo se hecha encima para tenerlo antes del ESOF 2008 no nos planteamos cambiar a TCP/IP. Al final se soluciona cambiando de mandar todo un televisor completo antes de pasar al siguiente, a mandar la linea n-esima a todos los televisores, antes de empezar con la (n+1)-esima. Se soluciona el problema de los mensajes perdidos y conseguimos una aplicación que muestra imágenes a una velocidad más que aceptable. Un éxito.

El código lo pongo a disposición del que lo pida. Aunque no es nada del otro mundo. Y la próxima semana, pondré imágenes de la feria.

20 de julio de 2008

Panel de Televisiones con PS3 (I)

En las próximas entradas voy a explicar un proyecto que me ha tenido ocupado las últimas semanas. Con motivo de la feria ESOF 2008 el Instituto de Física de Cantabria tenia apalabrado un stand donde enseñaría lo que se hace aquí. Como es una feria de divulgación científica, se decide llevar 9 televisores LCD que formaban un panel de 3x3. Este panel ya lo había preparado Ignacio Coterillo anteriormente con imágenes estáticas. Para la ESOF 2008 se preparo un programa para ir pasando imágenes sucesivamente.
En la entrada de hoy voy a hablar brevemente del hardware utilizado.

Implicados:
  • Ignacio Coterillo
  • Luis José Cabellos
Hardware:
  • 9 televisiones LCD 42'', Hitachi
  • 9 Playstation 3 de 40GB
  • 1 switch ethernet 3COM de 24 bocas
  • cables hdmi, cables ethernet, cables de alimentación...etc
Preparación de las PS3:
  • Instalar Fedora 7
  • Instalar un entorno de ventanas liviano: XFCE
  • Quitar servicios no necesarios: bluetooth, mail, ...etc
  • Se las configura con una red local para verse entre ellas a través del switch y se configura ssh para ir a través de claves RSA.
Próxima entrada, Software desarrollado para mostrar las imágenes.

15 de abril de 2008

Tarjeta de programador

El fin de semana pasada me entro una inquietud. Saber como quedaría una tarjeta de presentación/visita de un programador. Y que ademas la tarjeta de presentación fuese un programa. Así que me he puesto a ello y aquí presento el resultado.

Empecemos con un par de reglas para hacer la tarjeta:


  1. Todo el texto debe poder compilarse/ejecutarse

  2. La información normal de una tarjeta de visita debe de quedar clara en el texto

  3. El texto tiene que ser suficientemente corto como para caber en una tarjeta


Yo aparte, en mi caso añadí la siguiente regla, tiene que ser en Haskell. Y esta es la primera versión que se me ocurrió:

data Career = Actor | Programmer | TaxiDriver | Writer
deriving( Show )
type Name = String
type Contact = String

careerOf :: Name -> Maybe Career
careerOf "Luis Jose Cabellos Gomez" = Just Programmer
careerOf _ = Nothing

contactOf :: Name -> Contact
contactOf "Luis Jose Cabellos Gomez" = "zhen.sydow at gmail.com"

main = putStrLn.unlines $ map
(\f-> f "Luis Jose Cabellos Gomez")
[ id, show.careerOf, contactOf ]

Lo que me gusta del código anterior es que la definición de las funciones por reglas hace que se lean casi como frases. También me gusta el chiste con el monad Maybe, Luis es "Just a Programmer", no me digáis que no queda bien. Lo que no me acaba de gustar es que las funciones son muy artificiales, y se repite en muchos sitios mi nombre. Y luego la función principal es muy confusa. Y para una definición de tipos tan triste, mejor nos quedamos con las macros de C.

Pasemos a la segunda versión.

data Career = Actor | Programmer | TaxiDriver | Writer
deriving Show

data Person = Person { name :: String
, career :: Maybe Career
, contact :: String }
deriving Show

me = Person "Luis Jose Cabellos Gomez"
(Just Programmer)
"zhen.sydow at gmail.com"

main = putStrLn $ show me

La segunda versión tiene lo bueno de la primera versión (no me podía quedar sin el chiste del Maybe) y tiene un par de mejoras. La función principal es muy sucinta, y la información principal no esta tan desperdigada. Ademas de tener una definición de tipos más completa. Me quedo con esta versión.

Cuando ya tenía el texto, pues me puse a decorarlo un poco. Se me ocurrió imprimirlo como su fuese una carta de poker. Usando el Inkscape me hice un símbolo lambda y lo puse como si fuese el palo de la baraja. A partir de ahora el poker se juega con corazones, diamantes, picas, tréboles y lambdas. Lo puse todo sobre una imagen, puse en negrita los datos . Y ya esta, ya tenia tarjeta de programador. A imprimirlo.


Y este es el resultado final. Tengo que decir que la impresora no lo imprimió todo lo bien que me gustaría. Y quizás con una fuente un poco más clara el resultado hubiese sido mejor, pero....


... me pregunto si será posible hacer algo tan elegante en código C++.

UPDATES:


FIN UPDATES

27 de febrero de 2008

Evaluación Perezosa

Hoy quisiera hablar de una de las características de Haskell más fascinantes, la evaluación perezosa. Y me han entrado ganas de hablar de ello al ver la utilización que se da a la evaluación perezosa en el problema repMin de Richard Bird. El problema viene a ser el siguiente: reemplazar los valores en los nodos de un árbol por el mínimo valor de este mismo árbol, y solo recorriendo el árbol una sola vez.

Yo voy a explicarlo con una estructura más sencilla, sustituir todos los valores de una lista de números por el mínimo valor de esta lista, recorriendo la lista una sola vez. Y esto usando de manera muy inteligente la evaluación perezosa. Vamos a empezar contando que es la evaluación perezosa (lazy).

Evaluación Perezosa

En los lenguajes de programación tradicionales, lo que tenemos es evaluación ansiosa (eager). Con este tipo de evaluación, cuando asignamos un valor a una variable, o pasamos un parámetro a una función, se calcula cual es el valor final a asignar o cual es el valor final del parámetro. Por ejemplo:

v = sqrt(2) + 3 * sqrt(5);

f( 4 / g( 7 ) );

Antes de asignar nada a v, se llamara a las funciones sqrt con sus parámetros correspondientes, luego se aplicara la expresión matemática para obtener el valor final a asignar a v. En el caso de la llamada a la función f, antes se ha de llamar a la función g y dividir entre 4 el resultado de esta llamada. Por eso se denomina evaluación perezosa, porque se ha de obtener el valor de las expresiones en cuanto el programa se las encuentra.

La evaluación perezosa solo calcula el valor de las expresiones cuando necesita hacer uso de este valor. En el código de ejemplo anterior, en v se guardaría como se calcula su valor y no el valor final. Y el parámetro pasado a f sería la formula completa y no el valor de aplicar la expresión. Si resulta que ni la variable v ni el parámetro de la función f se usan, pues nunca se llamaría a las funciones sqrt y g, ni se realizarían las operaciones matemáticas pertinentes.

Esto puede suponer una diferencia enorme entre ejecutar con evaluación perezosa y evaluación ansiosa. Si tenemos definidas f y g como:

int f( int a ){
return 4;
}
int g( int a ){
return a - 7;
}

Con evaluación ansiosa obtendríamos un error "division by zero" mientras que con evaluación perezosa la ejecución terminaría sin error.

ReplaceMin

La primera versión que podemos pensar de replaceMin es la que pongo a continuación:

replaceMin xs = replace xs (minimum xs)
where replace xs m = map (\a -> m) xs

Primero se calcula el mínimo de la lista de números xs y luego se sustituye todos los valores de esta lista por el valor mínimo (usando la función replace). Esta primera versión recorre la lista dos veces, una vez para calcular el mínimo y una segunda vez para reemplazar los valores.

Y ahora viene lo verdaderamente impresionante. La versión de replaceMin que recorre la lista una sola vez:

replaceMin xs = ys
where (ys, m) = rpMin xs m

¿Y como funciona? Gracias a la evaluación perezosa. Entendiendo lo que hace la función rpMin, lograremos entender como funciona. La función rpMin tiene dos parámetros, una lista de números xs y un numero m. Lo que hace rpMin es sustituir todos los valores de xs por m (igual que replace) con el añadido de que a la vez va calculando el mínimo de xs. La función rpMin devuelve una tupla con la nueva lista con los valores sustituidos y con el mínimo de la antigua lista.

La magia ocurre en la manera de llamar a rpMin desde la función replaceMin, como valor a sustituir se le pasa el mínimo que todavía no ha calculado rpMin y del cual no necesita saber su valor. Básicamente la conversación entre las dos funciones seria:
  • replaceMin: sustituyeme todos los valores de xs por este valor m del que de momento no se nada
  • rpMin: ¿Pero como? ¡Necesitare saber el valor de m para ponerlo en la lista!
  • replaceMin: ¡No hombre, no! Recuerda que nosotros tenemos evaluación perezosa.
  • rpMin: Bueno, pues tu sabrás cual es el valor de m, ya se lo dirás al que quiera usar la lista resultante.
  • replaceMin: ¡Te vuelves a equivocar! El valor me lo dirás tu, cuando calcules el mínimo de xs.

El valor de m solo será necesario fuera de la función replaceMin, por ejemplo cuando se imprima la lista resultante por pantalla, y para entonces rpMin ya habrá calculado el mínimo.

Esta sería la definición de rpMin. Simplemente va sustituyendo los valores de la lista por el valor m pasado como parámetro, y mientras va calculando el mínimo comparando cada valor de la lista con el mínimo hasta ese momento. Y todo recorriendo la lista una sola vez.

rpMin [x] m = ([m], x)
rpMin (x:xs) m = (m:ys, min x my)
where (ys, my) = rpMin xs m

Bueno, os dejo pensando en porqué funciona. O podeis probarlo por vosotros mismos en vuestra distribución de Haskell favorita. También os dejo los enlaces a las versiones de replaceMin para arboles.

Referencias:
Functional Pearl: Trees
Solving the repMin problem with I/O

7 de diciembre de 2007

Simplificar Funciones en Haskell

Hoy voy a hablar un poco acerca de un proyecto propio, y de como usar las características de un lenguaje como Haskell para escribir menos. El proyecto es una calculadora de pila (primero se escriben los operandos y luego se escribe el operador) y la podéis encontrar en Hscalc.

El código que viene a continuación son las funciones ejecutadas cuando se pulsa sobre los diferentes botones de la calculadora. Por ejemplo la función pulsaNumero inserta un nuevo dígito en la pila.

pulsaNumero v entries n = do
val <- readIORef v
let newVal = insertaDigito val n
writeIORef v newVal
putStackInEntries entries newVal

pulsaComa v entries = do
val <- readIORef v
let newVal = insertaComa val
writeIORef v newVal
putStackInEntries entries newVal

pulsaSigno v entries = do
val <- readIORef v
let newVal = insertaSigno val
writeIORef v newVal
putStackInEntries entries newVal

pulsaStackAdd v entries = do
val <- readIORef v
let newVal = nullValue:convertValues val
writeIORef v newVal
putStackInEntries entries newVal

pulsaStackClear v entries = do
writeIORef v pilaVacia
putStackInEntries entries pilaVacia

pulsaOpBinaria v entries f = do
val <- readIORef v
let newVal = aplicaFuncion val f
writeIORef v newVal
putStackInEntries entries newVal

Podemos ver que se repite un mismo patrón en todas las funciones. Primero se obtiene el valor actual de la calculadora, se aplica una función que modifica este valor y luego se guarda el nuevo valor y se actualiza la ventana. Con este patrón nos hacemos nuestra función base, la cual toma como primer parámetro la función a aplicar para modificar el estado de la calculadora. Esta función base es pulsaFuncion.

pulsaFuncion funcion v entries = do
-- cojer valor actual
val <- readIORef v
-- aplicar una modificacion al valor
let newVal = funcion val
-- actualizar valor
writeIORef v newVal
-- actualizar ventana
putStackInEntries entries newVal

La primera característica que vamos a aprovechar de Haskell es la aplicación parcial. Con esta característica, podemos definir nuevas funciones fijando el valor de parte de los parámetros. De esta manera definimos pulsaComa y pulsaFuncion, fijando el primer parámetro. Al resto de los parámetros se les da valor a la hora de invocar las nuevas funciones.

pulsaComa = pulsaFuncion insertaComa
pulsaSigno = pulsaFuncion insertaSigno

Para el resto de funciones tenemos que usar funciones anónimas. Una función anónima permite definir funciones en cualquier lugar en donde se pueda usar una expresión. En Haskell, las funciones anónimas se definen de la siguiente manera.

-- función anónima con dos parámetros a y b
(\a b-> a+b)

Usando una función anónima definimos pulsaStackAdd con una función que dado un valor, le inserta un cero al principio.

pulsaStackAdd =
pulsaFuncion (\v-> nullValue:convertValues v)

Para la función pulsaStackClear, ignoramos el parámetro y siempre devolvemos una pila vacía. Aunque el código original de pulsaStackClear era menor, el resultado es el mismo al ser Haskell un programa de evaluación perezosa. No realiza la llamada para obtener el valor actual de la calculadora ya que luego no va a usar ese valor.

pulsaStackClear = pulsaFuncion (\_-> pilaVacia)

Para las funciones pulsaNumero y pulsaOpBinaria necesitamos un parámetro adicional, el resto es igual que en los casos anteriores.

pulsaNumero n = pulsaFuncion (\v-> insertaDigito v n)
pulsaOpBinaria f = pulsaFuncion (\v-> aplicaFuncion v f)

Con estos cambios el código se simplifica enormemente, aumentando en legibilidad y siendo mucho más fácil de mantener. Si queremos hacer algo adicional, con cambiar la función base nos bastaría.

pulsaFuncion funcion v entries = do
val <- readIORef v
let newVal = funcion val
writeIORef v newVal
putStackInEntries entries newVal

pulsaComa = pulsaFuncion insertaComa

pulsaSigno = pulsaFuncion insertaSigno

pulsaStackAdd =
pulsaFuncion (\v-> nullValue:convertValues v)

pulsaStackClear = pulsaFuncion (\_-> pilaVacia)

pulsaNumero n = pulsaFuncion (\v-> insertaDigito v n)

pulsaOpBinaria f = pulsaFuncion (\v-> aplicaFuncion v f)

7 de noviembre de 2007

Modelo-Vista-Controlador simplificado

Haskell es mi lenguaje preferido. Puede parecer una afirmación rotunda, pero al ser algo totalmente subjetivo, lo puedo decir sin equivocarme. Lo que no es tan subjetivo es la experiencia que tengo en Haskell. Casi nula, apenas nada. Por eso he decidido empezar un pequeño proyecto para adquirir soltura.

Haskell Stack Calculator. El proyecto en cuestión consiste en una calculadora de pila. Es decir, que primero se introducen los operandos para a continuación introducir el operador. Como primer paso, hago un esquema simple de lo que va a ser el diseño de la aplicación. Y me decido por usar el patrón Modelo-Vista-Controlador, MVC.

El patrón Modelo-Vista-Controlador más común es el del siguiente diagrama. En este diagrama se ven que las tres partes del patrón tienen relación entre si. La Vista avisa al Controlador de los eventos del usuario. El Controlador actualiza los datos del Modelo. Y el Modelo con los nuevos datos actualiza la Vista.


Lo primero que me doy cuenta es que con Haskell, el modelo va a ser un conjunto de funciones que vayan transformando los datos. En concreto, tendré funciones que cojan la pila actual y devuelva una nueva pila después de aplicar el operador correspondiente. Teniendo esto en cuenta, tenemos que la relación entre Modelo y Vista es innecesaria. Podemos hacer uso de un MVC simplificado, que es el que tenemos en el siguiente gráfico.


Teniendo en mente este gráfico reducido de MVC, podemos ver que la mayor parte de la lógica va a ir en el Controlador. La parte de Vista se actualizará utilizando el GUI adecuado (gtk2hs en nuestro caso). De este diseño preliminar podemos sacar una plantilla de las diferentes funciones del controlador:

pulsaBoton modelo vista = do
valor <- getValor modelo

-- usa el valor
-- .....
-- let nuevoValor = .....
setValor modelo nuevoValor
actualizaVista vista nuevoValor

De esta manera, a la hora de programar la calculadora, vamos usando la plantilla anterior para cada uno de los botones. Añadiendo parametros extra incluso podemos reusar la misma funcion para multiples botones. Por ejemplo, todos los botones númericos pueden usar la misma función.

En el caso de HSCalc, tal como esta implementado, se hace uso de IORef para mantener el estado de la pila. En este caso la función plantilla de la parte Controlador seria la siguiente.

pulsaBoton :: IORef m -> v -> IO()
pulsaBoton modelo vista = do
valor <- getIORef modelo

-- usa el valor
-- .....
-- let nuevoValor = .....

writeIORef modelo nuevoValor
actualizaVista vista nuevoValor

Bueno, uno de mis temores con Haskell es como realizar el flujo de un programa normal. Mediante este MVC simplificado se me ha hecho francamente fácil. En próximos posts ya iré contando como me va con Haskell Stack Calculator

La imagen del diagrama MVC ha sido obtenida de Wikipedia. La imagen del diagrama simplificado la he realizado yo a partir de la primera.

3 de octubre de 2007

Variables en Haskell

Hoy nos olvidamos un poco de comentar código mal hecho y vamos a hablar del mejor lenguaje del mundo. Haskell.

Una de las caracteristicas que más se comentan de Haskell es que no usa variables destructivas, es decir, que una vez asignado el valor a una variable este permanece inmutable. Esto hace difícil programar código con datos que cambien de estado durante la ejecución del programa. Vamos a ver una manera de mantener y cambiar el estado de una variable. Para empezar, aqui teneis el código que usare. No voy a comentar el código, solo el uso que le voy a dar para crear una variable mutable. Comentare un poco por encima porque funciona al final.

import Data.IORef

incrementa::(Num a)=>IORef a->a->IO a
incrementa v n = do
val <- readIORef v
writeIORef v (val + n)
return (val + n)

creaContador::(Monad m, Num a)=>IORef a->a->m (IO a)
creaContador v n = return (incrementa v n)


Vamos a cargar el código anterior en una sesion interactiva de Haskell (hugs, ghci, ...etc). Lo primero que hacemos es crear un contenedor con el valor cero (fijaos como esquivo llamarlo variable).

ghci> v <- newIORef 0

Luego, usando la funcion creaContador que hemos definido anteriormente, vamos a crear una funcion que mantendrá una cuenta de las veces que se ha llamado. Que facil, ¿no?.

ghci> tick <- creaContenedor v 1

Ahora, si invocamos tick cuatro veces, nos devolvera sucesivamente los valores 1, 2, 3, y 4.

ghci> tick
1
ghci> tick
2
ghci> tick
3
ghci> cuenta <- tick
4
ghci> putStrLn $ "Numero de veces = " ++ (show cuenta)
Numero de veces = 4

Lo primero que tenemos que mirar es v, que es una referencia a una variable mutable. Es decir, es un contenedor para un valor. El valor con el que comienza es cero. Lo de hablar de un contenedor en vez de valor es la manera que tiene Haskell de seguir manteniendo inmutable v, v (el contenedor) permanece inmutable, lo que cambia es el contenido. A estos contenedores se les denominan Monads.

La función incrementa devuelve un valor de tipo IO a. IO también es un contenedor, el valor devuelto es un contenedor con contenido de tipo a. Es de tipo IO porque realiza una computación con la variable mutable, calcula un valor nuevo y lo mete de nuevo en el contenedor.

Y llegamos a la última parte, la función tick. No es una función. También es un contenedor (monad) cuyo contenido es una computación. Cada vez que invocamos tick, lo único que estamos haciendo es volviendo a ejecutar la computación que guarda. Por eso se incrementa el valor referenciado.

Y para terminar con Haskell por hoy. Fijaos en la diferencia entre llamar a creaContenedor y llamar a incrementa directamente, en el siguiente código no se guarda en tick la computación, sino el valor resultante de realizar la computación. Otro día hablamos de las funciones para tratar con monads (contenedores) como do, return y <-.

ghci> tick <- incrementa v 1 
1
ghci> tick
1
ghci> tick
1
ghci> tick
1