I don't remember how I get interested in Haskell, but I remember that Why Haskell matters sealed the deal. There was a comparison of QuickSort implementations in C++ and Haskell. The Haskell version was so beautiful, so concise, that I had to learn it.
Even the first time I saw the comparison, I knew that it wasn't all fair. The algorithms weren't the same. The C++ version sorted an array in place. The Haskell acted upon lists. Of course, you can write a Haskell version that use mutable arrays. And you can, with some modification, implement the C++ version using lists, list concatenation and filter.
But why a C++ programmer would use a list structure when an array can do the work? Why a programmer would spend his time learning Haskell to then solve a problem imperatively?
Each language impose a culture, not only by forbidding thing, but by making other easy. Haskell not only gives you functions as firt-class citizens, but it makes so easy to compose, curry and uncurry them, that it is only natural to do it.
This composability is the real reason why to program in Haskell is a pleasure.
A corner to talk about software design, type theory, compilers and programming languages.
Friday, January 29, 2010
Thursday, January 28, 2010
Desarrollando con zc.buildout
En un post anterior comentaba que es zc.buildout, por qué puede necesitarlo un desarrollador y como instalarlo. Ahora hablaré sobre como desarrollar un proyecto usandolo.
buildout.cfg
buildout.cfg es el archivo que determina como se construirá el proyecto, cuales son sus partes y como generarlas. El formato del archivo es el tradicional .INI de windows, dividido en secciones (nombres encerrados entre corchetes) formadas por lineas con el formato clave=valor.
Así como hay récipes para instalar los paquetes o huevos de python, hay otros que instalan aplicaciones en C, como openldap o postgresql o para descargar proyectos vía CVS o git. El python package index tiene una larga lista de paquetes con récipes para zc.buildout.
Cuando hayas terminado con buildout.cfg puedes construir todas las dependencias de tu sistema con un simple comando:
Esto es todo lo que hay que saber de zc.buildout para usarlo en tus desarrollos. El resto es buscar las recetas que generen las partes que necesites. En última instancia, un récipe es simplemente un script en python, por lo que puedes crear uno nuevo si no consigues ninguno que satisfaga tus necesidades.
buildout.cfg
buildout.cfg es el archivo que determina como se construirá el proyecto, cuales son sus partes y como generarlas. El formato del archivo es el tradicional .INI de windows, dividido en secciones (nombres encerrados entre corchetes) formadas por lineas con el formato clave=valor.
La sección buildout es la única requerida en el archivo buildout.cfg. La primera linea[buildout]
develop = .
parts = dependencias
[dependencias]
recipe = zc.recipe.egg:eggs
eggs = python-ldap
docutils
develop = .indica cuales son los directorios con los paquetes de desarrollo (los paquetes o huevos en los que estamos trabajando y que todavía no hemos instalado). Cada uno de esos directorios debe tener un archivo setup.py que defina el huevo de python.
parts = dependenciasdice que este proyecto esta formado por una única parte, llamada "dependencias". Cada parte deberá tener su propia sección donde se indique como construirla. En nuestro ejemplo
[buildout]zc.recipe.egg es un paquete que define un récipe con nombre eggs . Este recipe es un programa encargado de construir la parte dependencias. Lo que hace este recipe en particular es descargar los paquetes python-ldap y docutils. Nota que una linea que comienza con espacios significa que es la continuación de la linea anterior.
develop = .
parts = dependencias
[dependencias]
recipe = zc.recipe.egg:eggs
eggs = python-ldap
docutils
Así como hay récipes para instalar los paquetes o huevos de python, hay otros que instalan aplicaciones en C, como openldap o postgresql o para descargar proyectos vía CVS o git. El python package index tiene una larga lista de paquetes con récipes para zc.buildout.
Cuando hayas terminado con buildout.cfg puedes construir todas las dependencias de tu sistema con un simple comando:
{ ubicado en la raiz de tu proyecto}Deberas ejecutar buildout cada vez que agregues una nueva dependencia y hayas editado buildout.cfg. Pero lo más importante es que tus usuarios también podrán instalar todas las dependencias de tu aplicación ejecutando buildout.
$ bin/buildout
Esto es todo lo que hay que saber de zc.buildout para usarlo en tus desarrollos. El resto es buscar las recetas que generen las partes que necesites. En última instancia, un récipe es simplemente un script en python, por lo que puedes crear uno nuevo si no consigues ninguno que satisfaga tus necesidades.
Friday, January 22, 2010
A monad non-tutorial
...or why you shouldn't ask what a monad is
"What is a monad?" is one of the most common question when you're learning Haskell. And it's there when troubles start, because it is the wrong question. Ask a wrong question, and you'll get the wrong answer. The only right answer to this question is a mathematic definition:
After a reverential minute of silence, you start to figure it out that maybe you shouldn't be asking that. Your mind is working fast to come out with a phrase to fool your interlocutor into thinking that you have understood. But there's nothing to understand.
You'll see, that's the way definitions work. They are use to label objects, that's the only thing they are good for. So if the definition of homo sapiens is "an animal, mammal, of the order of primates, family hominidae and genus homo" and if you know what is an animal, a mammal, a primate... etc. then you can put objects into classes: "my friend Ed, homo sapiens; mi dog Buch, no".
It is why, not what
The least interesting thing about monads is its definition. The real question is "why?". You only make a definition if it is useful, if you use it frequently. So, let's see how this definition is implemented in Haskell:
An that is a good thing. After all, FileishInterface is so concrete that it could be used only in a very few cases. The interface of Monad is so generic that it could be used in a wide range of objects. Paradoxically, that make it hard to think in a use when you are learning the concept.
When
The other problem when you are learning monad is that you want to use them. You open your editor and say "I'll make a program and I'm gonna create my own monads and I'll show it in the Haskell Café and I'll become a member of the Secret Category Cabal and I'm gonna spend my evenings talking about Kleili arrows and functors and maybe Paul Hudak will friend me in facebook". Curiously, you have never say "Mmm, today I feel like I'm gonna make a program that uses hashtables". You don't try to impose data structures to your imperative programming, so why are you trying to force monads in your haskell programs? Don't go after monads, let the monads find you.
You'll know when you need them. One day you will make a program and you'll write a type constructor:
If you use your type constructor M to create only one type, say M Integer, then you don't need monads. But, after a few hours of programming, types start to appear and you have M Char, M String, M Bool and others. After a few coups of coffee you have functions all over the place with types like Integer -> M Integer, Integer -> M Bool, Bool -> M Char.
Then you need to chain these function together, but it's a pain in the functional ass. Each time you need to connect two functions, say Integer -> M Bool and Bool -> M Char, you need to invoke the first function, extract the value inside M and invoke the second one. And it is then --I hope-- that the "aha! moment" comes to you. You can write a function that extract the a from M a and use it to invoke a function with type a -> M b. You have invented >>=, the bind function!
Since you wrote the type constructor M, you are the one who know how to extract a from M a. And you know how to chain that value to a -> M b. Each monad has its own definition of >>=, adapted to its structure.
When is return used? If you see the >>= definition, it needs a M a to start. If you have a value of type a instead, you'll need a way to transform it into M a. That's what return does.
Now what?
Hopefully you know by now that the definition of monads is not the important part for a programmer. You only need to focus in the type constructor and the bind function. What does the type constructor represent? What is it used for? How they defined bind: how it extract the value inside one of those new types and how it chain it to the a -> m b function?
So, now you can go back to those tutorials that explain what are monads and give them a second look. You can review the do notation that help you to write your long chain of >>= expression in a clear way. Good luck.
The IO Monad
But wait! I can't end this post without talking about IO monads, can I? A monad is a monad is a monad. IO monads are not the exception. Since your understanding of monad is better now and my hands are tired, I'll cheat a little.
The IO a data constructor can be seen as a function that takes a value of type a and returns a C program. When that program is compiled and executed, it will return that value. That's the work of the IO bind. It compiles and executes the first argument (IO a), gets the a values and feed it to its second argument (a -> IO b) which returns a new C program that will return a value of type b. That's why the haskellers can see you right into the eyes and say that Haskell is a pure language: "Of course that putChar is a pure function. Giving the same input, it will always return the same C program!".
Thinking in this way is useful to understand why IO is a monad, but it is a waste of your attention. In your daily programming, you'll be better thinking that the IO values are an imperative language embedded in haskell and >>= is its interpreter.
"What is a monad?" is one of the most common question when you're learning Haskell. And it's there when troubles start, because it is the wrong question. Ask a wrong question, and you'll get the wrong answer. The only right answer to this question is a mathematic definition:
[...] A monad is a triple (M, unitM, bindM) consisting in a type constructor M and a pair of polymorphic functions {unitM :: a -> M a} and {bindM :: M a -> (a -> M b) -> M b}
These functions must satisfy three laws [...]
(unitM a) `bindM` k = k a
m `bindM` unitM = m
m `bindM` (\a -> (k a) `bindM` (\b -> h b)) =
(m `bindM` \a -> (k a)) `bindM` (\b -> h b)
After a reverential minute of silence, you start to figure it out that maybe you shouldn't be asking that. Your mind is working fast to come out with a phrase to fool your interlocutor into thinking that you have understood. But there's nothing to understand.
You'll see, that's the way definitions work. They are use to label objects, that's the only thing they are good for. So if the definition of homo sapiens is "an animal, mammal, of the order of primates, family hominidae and genus homo" and if you know what is an animal, a mammal, a primate... etc. then you can put objects into classes: "my friend Ed, homo sapiens; mi dog Buch, no".
It is why, not what
The least interesting thing about monads is its definition. The real question is "why?". You only make a definition if it is useful, if you use it frequently. So, let's see how this definition is implemented in Haskell:
class Monad m whereSo, a monad is an Haskell class. Or, if you are a Java guy (and let's confess it, who isn't?), then you can think in a monad as an interface. But, again, why? When you see FileishInterface with its methods open, close, read, write and seek, you say: "Oh, so FileishInterface represents objects that behave like a file, I get it". You can think in a FileishInterface instance using a TCP connection or a byte array. With monads, there is nothing in its interface that make you think in a concrete example.
(>>=) :: m a -> (a -> m b) -> m b
return a :: m a
An that is a good thing. After all, FileishInterface is so concrete that it could be used only in a very few cases. The interface of Monad is so generic that it could be used in a wide range of objects. Paradoxically, that make it hard to think in a use when you are learning the concept.
When
The other problem when you are learning monad is that you want to use them. You open your editor and say "I'll make a program and I'm gonna create my own monads and I'll show it in the Haskell Café and I'll become a member of the Secret Category Cabal and I'm gonna spend my evenings talking about Kleili arrows and functors and maybe Paul Hudak will friend me in facebook". Curiously, you have never say "Mmm, today I feel like I'm gonna make a program that uses hashtables". You don't try to impose data structures to your imperative programming, so why are you trying to force monads in your haskell programs? Don't go after monads, let the monads find you.
You'll know when you need them. One day you will make a program and you'll write a type constructor:
type M a = ....(did you notice how your type constructor's name is "M" as in "Monad"? This is a clever, subtle way in which the author -- me -- hints that you've started to write a monad).
If you use your type constructor M to create only one type, say M Integer, then you don't need monads. But, after a few hours of programming, types start to appear and you have M Char, M String, M Bool and others. After a few coups of coffee you have functions all over the place with types like Integer -> M Integer, Integer -> M Bool, Bool -> M Char.
Then you need to chain these function together, but it's a pain in the functional ass. Each time you need to connect two functions, say Integer -> M Bool and Bool -> M Char, you need to invoke the first function, extract the value inside M and invoke the second one. And it is then --I hope-- that the "aha! moment" comes to you. You can write a function that extract the a from M a and use it to invoke a function with type a -> M b. You have invented >>=, the bind function!
Since you wrote the type constructor M, you are the one who know how to extract a from M a. And you know how to chain that value to a -> M b. Each monad has its own definition of >>=, adapted to its structure.
When is return used? If you see the >>= definition, it needs a M a to start. If you have a value of type a instead, you'll need a way to transform it into M a. That's what return does.
Now what?
Hopefully you know by now that the definition of monads is not the important part for a programmer. You only need to focus in the type constructor and the bind function. What does the type constructor represent? What is it used for? How they defined bind: how it extract the value inside one of those new types and how it chain it to the a -> m b function?
So, now you can go back to those tutorials that explain what are monads and give them a second look. You can review the do notation that help you to write your long chain of >>= expression in a clear way. Good luck.
The IO Monad
But wait! I can't end this post without talking about IO monads, can I? A monad is a monad is a monad. IO monads are not the exception. Since your understanding of monad is better now and my hands are tired, I'll cheat a little.
The IO a data constructor can be seen as a function that takes a value of type a and returns a C program. When that program is compiled and executed, it will return that value. That's the work of the IO bind. It compiles and executes the first argument (IO a), gets the a values and feed it to its second argument (a -> IO b) which returns a new C program that will return a value of type b. That's why the haskellers can see you right into the eyes and say that Haskell is a pure language: "Of course that putChar is a pure function. Giving the same input, it will always return the same C program!".
Thinking in this way is useful to understand why IO is a monad, but it is a waste of your attention. In your daily programming, you'll be better thinking that the IO values are an imperative language embedded in haskell and >>= is its interpreter.
Thursday, January 21, 2010
zc.buildout
Comienzo hoy una serie de artículos sobre desarrollo de aplicaciones en python, particularmente, desarrollo web. Y nuestro invitado del día es zc.buildout. O simplemente buildout, para los amigos.
A buildout lo había mencionado de pasada en otras oportunidades. Ahora es el momento de tratarlo con exclusividad, como se merece.
Que problema soluciona buildout?
Desarrollar una aplicación para distribuirla tiene una serie particular de problemas. Los equipos donde se instale pueden no tener todas las aplicaciones de las que esta depende. O pueden tener versiones viejas o incompatibles. Este problemas se agrava cuando desarrollamos en un lenguaje de scripts. Aquí las dependencias suelen ser librerías desarrolladas con ese mismo lenguaje. Tristemente, ninguna distribución puede ofrecer una versión actualizada de todos los proyectos hechos en python, perl o ruby.
Esta situación le deja al programador (es decir a ti) la necesidad de hacer que el instalador de su aplicación busque e instale todas las dependencias, en sus versiones correctas. Esto distrae al programador de su tarea real: desarrollar la mejor aplicación posible.
Y precisamente este es el trabajo de buildout: asegurarse de conseguir e instalar todas las piezas de software que tu aplicación necesita.
Como funciona
Buildout se encarga de ensamblar todas las partes que necesita tu aplicación. Esto lo logra esto a traves de un lenguaje declarativo y extensible. Una serie de "recetas" pre-establecidas resuelven los problemas típicos del manejo de dependencias. Así que en la mayoría de los casos basta con indicarle tus necesidades y buildout se encargará en resolverlas.
La parte extensible entra en juego cuando tu aplicación necesita algo inusual. Entonces programas una nueva receta (en python) que resuelva esa dependencia y listo. El mundo tiene una nueva receta de buildout que puede ser usada por otros desarrolladores y cada día el trabajo con buildout se facilita.
Ejemplo práctico
El primer paso es, desde luego instalar buildout:
develop-eggs/ es un directorio manejado por buildout y que no necesitamos tocar directamente. Aquí habrá un enlace simbólico a cada paquete en desarrollo que necesitemos. Un paquete en desarrollo es, como su nombre lo indica, un paquete sobre el cual estamos trabajando todavía. Los paquetes en desarrollo no están instalados en el sistema y normalmente son inaccesibles a otros paquetes. Es por eso que buildout les ha dedicado todo un directorio: para poder acceder a ellos sin necesidad de instalarlos.
En eggs/ irán todos los paquetes o huevos de los que nuestra aplicación necesite. De nuevo, buildout los bajará por nosotros y verificará en cada ocasión, de ser necesario, que estén en su última versión.
parts/ es usado por los récipes. Aquí es donde se compilará, por ejemplo, una aplicación en C, que necesite nuestra aplicación.
Cuando empaquetemos nuestra aplicación, el programa empaquetador de python (setuptools) creará otros directorios, no relacionados con buildout.
Finalmente, está el archivo buildout.cfg. Para nosotros como desarrolladores, este es el corazón de buildout. Aquí indicaremos todas las instrucciones necesarias para que nuestra aplicación encuentre un entorno donde pueda correr sin problemas. Indicaremos cuales librerías en python necesita, y si estas son estables o no. Le diremos que aplicaciones en C, C++ u otro lenguaje tendrá que descargar, compilar e instalar, si ese fuera el caso. Le diremos si necesitamos crear una base de datos y cual debe ser su contenido. En fin, buildout.cfg nos dice todo lo que necesitamos saber de las dependencias de nuestra aplicación.
El contenido de buildout.cfg lo trataré en la próxima entrega.
A buildout lo había mencionado de pasada en otras oportunidades. Ahora es el momento de tratarlo con exclusividad, como se merece.
Que problema soluciona buildout?
Desarrollar una aplicación para distribuirla tiene una serie particular de problemas. Los equipos donde se instale pueden no tener todas las aplicaciones de las que esta depende. O pueden tener versiones viejas o incompatibles. Este problemas se agrava cuando desarrollamos en un lenguaje de scripts. Aquí las dependencias suelen ser librerías desarrolladas con ese mismo lenguaje. Tristemente, ninguna distribución puede ofrecer una versión actualizada de todos los proyectos hechos en python, perl o ruby.
Esta situación le deja al programador (es decir a ti) la necesidad de hacer que el instalador de su aplicación busque e instale todas las dependencias, en sus versiones correctas. Esto distrae al programador de su tarea real: desarrollar la mejor aplicación posible.
Y precisamente este es el trabajo de buildout: asegurarse de conseguir e instalar todas las piezas de software que tu aplicación necesita.
Como funciona
Buildout se encarga de ensamblar todas las partes que necesita tu aplicación. Esto lo logra esto a traves de un lenguaje declarativo y extensible. Una serie de "recetas" pre-establecidas resuelven los problemas típicos del manejo de dependencias. Así que en la mayoría de los casos basta con indicarle tus necesidades y buildout se encargará en resolverlas.
La parte extensible entra en juego cuando tu aplicación necesita algo inusual. Entonces programas una nueva receta (en python) que resuelva esa dependencia y listo. El mundo tiene una nueva receta de buildout que puede ser usada por otros desarrolladores y cada día el trabajo con buildout se facilita.
Ejemplo práctico
El primer paso es, desde luego instalar buildout:
$ sudo easy_install zc-buildoutEn otro post comenté como instalar easy_install. Buildout depende exclusivamente de easy_install y es todo lo que el desarrollador necesita para manejar el problema de las dependencias. Sin embargo, el usuario final podría no tener instalado zc-buildout, por lo cual el recomendable agregar a tu proyecto un pequeño script que se encargue de conseguirlo:
# Bajamos bootstrapOk, ya tenemos instalado buildout y bootstrap, ahora solo necesitamos un proyecto en python:
$ wget 'http://svn.zope.org/*checkout*/buildout-website/trunk/bootstrap.py'
# Y lo guardamos en un lugar seguro para
#agregarlo a cada poyecto que use buildout
$ mv bootstrap.py ~/bin
# Creamos nuestro nuevo proyectoEsto creará los siguientes objetos en nuestro directorio:
$ mkdir miproyecto
$ cd miproyecto
# Inicializamos buildout
$ buildout init
# ...y copiamos bootstap.py para
# beneficio de nuestros usuarios
$ cp ~/bin/bootstrap.py .
bin/En bin/ esta una copia de buildout. Aquí se instalarán los comandos de todos los huevos (paquetes en python) que instalemos.
develop-eggs/
eggs/
parts/
buildout.cfg
develop-eggs/ es un directorio manejado por buildout y que no necesitamos tocar directamente. Aquí habrá un enlace simbólico a cada paquete en desarrollo que necesitemos. Un paquete en desarrollo es, como su nombre lo indica, un paquete sobre el cual estamos trabajando todavía. Los paquetes en desarrollo no están instalados en el sistema y normalmente son inaccesibles a otros paquetes. Es por eso que buildout les ha dedicado todo un directorio: para poder acceder a ellos sin necesidad de instalarlos.
En eggs/ irán todos los paquetes o huevos de los que nuestra aplicación necesite. De nuevo, buildout los bajará por nosotros y verificará en cada ocasión, de ser necesario, que estén en su última versión.
parts/ es usado por los récipes. Aquí es donde se compilará, por ejemplo, una aplicación en C, que necesite nuestra aplicación.
Cuando empaquetemos nuestra aplicación, el programa empaquetador de python (setuptools) creará otros directorios, no relacionados con buildout.
Finalmente, está el archivo buildout.cfg. Para nosotros como desarrolladores, este es el corazón de buildout. Aquí indicaremos todas las instrucciones necesarias para que nuestra aplicación encuentre un entorno donde pueda correr sin problemas. Indicaremos cuales librerías en python necesita, y si estas son estables o no. Le diremos que aplicaciones en C, C++ u otro lenguaje tendrá que descargar, compilar e instalar, si ese fuera el caso. Le diremos si necesitamos crear una base de datos y cual debe ser su contenido. En fin, buildout.cfg nos dice todo lo que necesitamos saber de las dependencias de nuestra aplicación.
El contenido de buildout.cfg lo trataré en la próxima entrega.
Wednesday, January 20, 2010
Por qué git
Lo confieso: como desarrollador solitario, nunca había sido amigo del control de versiones. Como serlo, si requería tener corriendo un servicio, con las configuraciones y reconfiguraciones que ello conlleva (por mi manía de siempre probar la última distro)? Jamás me sentí a gusto con CVS, sin poder saber a ciencia cierta por qué.
Hasta que conocí git. Primero que nada, git es una aplicación. No requiere tener un servidor corriendo. Tu repositorio esta en el mismo directorio que tu proyecto, por lo que el respaldo es mas sencillo. Solo tipeas los comandos y ya!
En segundo lugar, es distribuido. Trabajo al menos en un par de computadores, sin contar una que otra laptop que uso de vez en vez contra mi voluntad. Puedo tener en cada equipo mi proyecto con toda su historia, trabajar independientemente y luego sincronizar con los demás. Cada PC es un equipo de desarrollo tan igual como los demás.
En tercer lugar, puedo elegir la estructura de mis repositorios, como se organizan jerarquicamente. En mi ciclo de trabajo personal elegí no tener un repositorio central, pero en el trabajo, donde más de una persona puede hacer cambios en un proyecto, usamos un maestro.
Pero la característica matadora de git son definitivamente sus branches. Hacer una rama de un proyecto es tan natural, tan barata, que forma parte integral de la forma de desarrollo en git. No puedo resaltar suficientemente este hecho: git me ha hecho un mejor usuario de los sistemas de versiones.
Como es esto? Dado que en mis experiencias previas, ramificar el proyecto era doloroso, tenía una única rama, donde aplicaba todos los cambios. Si trabajaba en tres aspectos distintos de una aplicación, todos los cambios iban al directorio de trabajo. De esta manera, los commits contenían cambios no relacionados. Hacer un commit era básicamente, hacer un respaldo del proyecto.
Fue usando las ramas de git que obtuve la iluminación: un aspecto, una rama. Aspecto culminado, commit. Apareció un bug que debo atender? Pues creo una nueva rama, corrijo el bug, pruebo y hago el commit y merge o rebase. Regreso a la rama inicial y sigo trabajando con el aspecto original.
Cada commit debe ser un milestone, un hito significativo en el desarrollo del programa "Agregue la caracteristica X", "Corregí el bug Y", "Cambié el algoritmo Z". Y no como antes "Toqué los archivos A, B, C, D y E porque estoy trabajando en X, Y y Z", repetido día tras día..
Hasta que conocí git. Primero que nada, git es una aplicación. No requiere tener un servidor corriendo. Tu repositorio esta en el mismo directorio que tu proyecto, por lo que el respaldo es mas sencillo. Solo tipeas los comandos y ya!
En segundo lugar, es distribuido. Trabajo al menos en un par de computadores, sin contar una que otra laptop que uso de vez en vez contra mi voluntad. Puedo tener en cada equipo mi proyecto con toda su historia, trabajar independientemente y luego sincronizar con los demás. Cada PC es un equipo de desarrollo tan igual como los demás.
En tercer lugar, puedo elegir la estructura de mis repositorios, como se organizan jerarquicamente. En mi ciclo de trabajo personal elegí no tener un repositorio central, pero en el trabajo, donde más de una persona puede hacer cambios en un proyecto, usamos un maestro.
Pero la característica matadora de git son definitivamente sus branches. Hacer una rama de un proyecto es tan natural, tan barata, que forma parte integral de la forma de desarrollo en git. No puedo resaltar suficientemente este hecho: git me ha hecho un mejor usuario de los sistemas de versiones.
Como es esto? Dado que en mis experiencias previas, ramificar el proyecto era doloroso, tenía una única rama, donde aplicaba todos los cambios. Si trabajaba en tres aspectos distintos de una aplicación, todos los cambios iban al directorio de trabajo. De esta manera, los commits contenían cambios no relacionados. Hacer un commit era básicamente, hacer un respaldo del proyecto.
Fue usando las ramas de git que obtuve la iluminación: un aspecto, una rama. Aspecto culminado, commit. Apareció un bug que debo atender? Pues creo una nueva rama, corrijo el bug, pruebo y hago el commit y merge o rebase. Regreso a la rama inicial y sigo trabajando con el aspecto original.
Cada commit debe ser un milestone, un hito significativo en el desarrollo del programa "Agregue la caracteristica X", "Corregí el bug Y", "Cambié el algoritmo Z". Y no como antes "Toqué los archivos A, B, C, D y E porque estoy trabajando en X, Y y Z", repetido día tras día..
Monday, January 18, 2010
Ya salió el libro de repoze.bfg
Ya salió el libro de repoze.bfg, el interesante entorno de desarrollo en python+WSGI. repoze.bfg es un framework minimalista inspirado en zope. Comparativamente ofrece las siguientes ventajas:
- Fácil de entender: Al ser un framework pequeño, puede ser entendido con menor esfuerzo.
- Familiaridad: Si ya has programado en zope, repoze.bfg es el paraíso: es zope2 bien hecho.
- Usa WSGI: Lo cual le permite interoperar más fácilmente con otras aplicaciones (aunque la última versión de zope parece que vendrá por defecto para usar WSGI).
- Pagas solo por lo que comes: Viene sin soporte para base de datos, autenticación, autorización o manejo de sesión. Toda esta funcionalidad esta soportada por proyectos independientes.
- Rápido: Dado que las aplicaciones desarrolladas en bfg no contienen componentes innecesarios, su velocidad de respuesta aumenta.
- No TTW: Zope2 ganó una inmensa popularidad al permitir el desarrollo de aplicaciones a través del web. bfg es un framework para programadores exclusivamente.
- Pocos componentes integrados: Fiel a su idea original de minimalismo, es responsabilidad del programador decidir sobre los componentes básicos de su aplicación. Cada vez. Por cada componente. Incluso en las pruebas más triviales.
Más diversión con tipos algebraicos
En otra entrada comenté la elegancia de los tipos de datos algebraicos (ADT) y como este simple concepto reemplaza a las enumeraciones, estructuras y uniones de otros lenguajes. Adicionalmente, los ADT ofrecen otra característica interesante: el emparejamiento de patrones (pattern matching).
En principio, los elementos de un ADT solo pueden ser accedidos por medio del emparejamiento de patrones. Veamos un ejemplo con un tipo de datos lista:
Los valores de tipo Lista están compuestos, o bien por el valor Nulo, o bien por un par ordenado "marcado" por Cons, cuyo primer elemento es de tipo a y su segundo es de tipo Lista a. Estos dos "marcadores" forman la base para el emparejamiento de patrones. Por ejemplo, para calcular la longitud de una lista:
Una ventaja del emparejamiento de patrones, es que podemos ver con claridad cuando nuestras funciones manejan todos los casos posibles. El compilador puede advertirnos cuando obviemos alguno.
Adicionalmente, como dividimos nuestra función longitud en dos expresiones, resulta más fácil de leer. Podemos concentrarnos en las peculiaridades de cada patrón por separado.
Cabe destacar que el emparejamiento de patrones se puede usar también para separar casos de acuerdo a los valores dentro de los ADT, no solo por sus "marcadores". Por ejemplo, la función elem que devuelve el i-simo elemento de una lista:
En principio, los elementos de un ADT solo pueden ser accedidos por medio del emparejamiento de patrones. Veamos un ejemplo con un tipo de datos lista:
data Lista a = Nulo | Cons a (Lista a)Nota como el tipo de datos Lista es polimórfica (define listas de enteros, reales o cualquier otro tipo) y recursiva (una lista de tipo a esta formada por un valor de tipo a unida a una lista de tipo a).
lista1 = Nulo
lista2 = Cons 3 Nulo
lista3 = Cons 7.3 (Cons 4.0 Nulo)
Los valores de tipo Lista están compuestos, o bien por el valor Nulo, o bien por un par ordenado "marcado" por Cons, cuyo primer elemento es de tipo a y su segundo es de tipo Lista a. Estos dos "marcadores" forman la base para el emparejamiento de patrones. Por ejemplo, para calcular la longitud de una lista:
longitud Nulo = 0Estas dos lineas combinadas, forman la definición de la función longitud. Utilizamos la primera solamente cuando se invoca longitud con la lista vacia (valor Nulo) y la segunda en los otros casos (valor Cons).
longitud Cons a b = 1 + longitud b
Una ventaja del emparejamiento de patrones, es que podemos ver con claridad cuando nuestras funciones manejan todos los casos posibles. El compilador puede advertirnos cuando obviemos alguno.
Adicionalmente, como dividimos nuestra función longitud en dos expresiones, resulta más fácil de leer. Podemos concentrarnos en las peculiaridades de cada patrón por separado.
Cabe destacar que el emparejamiento de patrones se puede usar también para separar casos de acuerdo a los valores dentro de los ADT, no solo por sus "marcadores". Por ejemplo, la función elem que devuelve el i-simo elemento de una lista:
elem 0 (Cons a b) = aEn este caso, solo aplicamos la primera expresión cuando pidamos el elemento cero de una lista.
elem i (Cons a b) = elem (i-1) b
Thursday, January 14, 2010
Diversión con tipos de datos algebraícos
Los tipos de datos algebraicos o ADT (por sus siglas en inglés: Algebraic Data Types) son una de las características más interesante de los "nuevos" lenguajes funcionales, y por nuevos quiero decir de la época de SML. Lo simpático de los ADTs es que reunen en un solo concepto lo que en otros lenguajes serían enumeraciones, estructuras y uniones.
Por ejemplo, las enumeraciones. Una enumeración es básicamente un conjunto de constantes utilizados para representar los posibles valores de un tipo dado. Para representar los días de la semana en C, utilizaríamos:
En Haskell, el código anterior sería:
Otro tipo de datos popular son las estructuras, que no son mas que un conjunto ordenado de valores de (posiblemente) diferentes tipos. En C una estructura se define del siguiente modo:
En Haskell, el tipo Persona esta formado por la etiqueta 'Persona' (llamado constructor de datos) y seguido por tres valores: dos strings y un entero.
Finalmente están las uniones. Una unión es una estructura de datos cuyos valores pueden pertenecer a dos o más tipos diferentes. Por ejemplo, en C:
Otra ventaja de los ADT viene, al menos para mí, del aspecto sicológico. Mientras que en C, fiel a su tradición de eficiencia y cercanía a la "maquina desnuda", estas estructuras de datos definen alineamientos en memoria, los ADTs ofrecen una definición más abstracta. Leer un ADT es leer las estructuras de datos del problema en su propio lenguaje. Leer un enum, union o struct de C, es leer la traducción de las estructuras de datos al lenguaje del computador.
Por ejemplo, las enumeraciones. Una enumeración es básicamente un conjunto de constantes utilizados para representar los posibles valores de un tipo dado. Para representar los días de la semana en C, utilizaríamos:
enum diasemana {Solo que en C, los enums son azúcar sintáctica, es una forma abreviada de definir un grupo de constantes. diasemana no es de ninguna forma un tipo de datos, son simplemente enteros.
DOMINGO, LUNES, MARTES, MIERCOLES,
JUEVES, VIERNES, SABADO
};
En Haskell, el código anterior sería:
data Diasemana = Domingo | Lunes | Martes | Miercolesla diferencia está en que, Diasemana si es un tipo de datos. Los valores de tipo Diasemana no son equivalentes a los enteros y no pueden mezclarse.
| Jueves | Viernes | Sabado
Otro tipo de datos popular son las estructuras, que no son mas que un conjunto ordenado de valores de (posiblemente) diferentes tipos. En C una estructura se define del siguiente modo:
typedef struct student_type {en Haskell sería:
char nombre[20];
char apellido[20];
int edad; } persona;
data Persona = Persona String String Int
En Haskell, el tipo Persona esta formado por la etiqueta 'Persona' (llamado constructor de datos) y seguido por tres valores: dos strings y un entero.
Finalmente están las uniones. Una unión es una estructura de datos cuyos valores pueden pertenecer a dos o más tipos diferentes. Por ejemplo, en C:
typedef struct {float real, imaginario; } cartesiano;En Haskell:
typedef struct {float modulo, angulo; } polar;
union {
cartesiano c;
polar p;
} complejo;
data Complejo = Cartesiano Float Float | Polar Float FloatComo se ve, usando solo los ADT se logra lo que en otros lenguajes requiere tres tipos diferentes de estructura de datos. Y no solo eso, sino que el resultado es más compacto.
Otra ventaja de los ADT viene, al menos para mí, del aspecto sicológico. Mientras que en C, fiel a su tradición de eficiencia y cercanía a la "maquina desnuda", estas estructuras de datos definen alineamientos en memoria, los ADTs ofrecen una definición más abstracta. Leer un ADT es leer las estructuras de datos del problema en su propio lenguaje. Leer un enum, union o struct de C, es leer la traducción de las estructuras de datos al lenguaje del computador.
Subscribe to:
Posts (Atom)