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

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.