Wednesday, June 9, 2010

My OOP Oh! moment.

Inheritance as the natural control structure in OOL

A few days ago I had my Object Oriented Programming "Oh! moment". I retook my studies of smalltalk reading Ragnar Hojland's LDAPlayer package. I didn't understood its design. Its LDAP-filter category seem to have a class for each possible variation of LDAP filter. A class for each variation of LDAP filter, for god's sake!. The same thing with the LDAP messages or the BER elements. A lot of classes were empty but for a single overloaded method. What a waste!

If I hadn't found Kent Beck's Smalltalk Best Practice Patterns I would remain in ignorance for more time. In the Choosing Message Pattern he says:
Procedural programs implement variations with conditional logic, either if-then-else or case statements. Two problems arise from such hard-coded logic. First, you cannot add a new variation without modifying the logic. This can be a ticklish operation, getting the new variation is without disturbing the existing variations. Second, such logic tends to propagate. You do not have to account for the variation in one place, you have to account for it in several. Adding a new variation means tickling all of the places where the logic lives.

Messages provide a disciplined way to handle theme-and-variation programming. Because the variations live in different objects, they have much less opportunity to interfere with each other than just putting the variations in different parts of the same routine. The client, the object invoking the variations, is also isolated from what variation is currently active.

Adding a new variation is as simple as adding a new object that provides the same set of messages the other variations provide and introducing it to the object that wants to invoke the variations.
If you are choosing between several action according to the parameter's class, then you should convert those actions in a new methods in each one of the respective classes and make an unconditional invocation to the new method. In this way you remove a case statement.

In the same way, if you are choosing between action according to the parameter's value, it is an indication that you should subclass the parameter's class. In this way, an LDAPFilterSingle becomes LDAPFilterWIthNot, LDAPFilterWithAnd and so many others.

The problem with OO teaching is that it makes emphasis in subclases as subtypes and the Liskov substitution principle. With this point of view, your most important work as a designer is to define the correct taxonomy of your objects, and that's one of the most difficult problems that exists. If you are forced to classify all the future types of your system today, you're screwed. Subclasses as subtypes is not the way inheritance was intended to be used. You make a subclass to share common code. You make a subclass to overload a method, so you can avoid an if..then or a case. A subclass is not an ontologic statement about the nature of the instances.

Open Classes goodies

So, subclassing and overloading allows you to reduce the control structures in your methods. But the goodies don't end there. Since in smalltalk all the classes are open (you can add your own methods), this pattern is valid even for classes outside your project. So, the base classes of the system become more powerful with the time.

That's being in another league regards flexibility.

Side note: Ruby has open classes too. As a python programmer I've always seen Ruby as a language too similar to python to be worth to learn it. Now, I feel a little Ruby envy.

Friday, January 29, 2010

Language's culture

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.

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 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.
develop = .
parts = dependencias

recipe = zc.recipe.egg:eggs
eggs = python-ldap
La sección buildout es la única requerida en el archivo buildout.cfg. La primera linea
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 que defina el huevo de python.
parts = dependencias
dice 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
develop = .
parts = dependencias

recipe = zc.recipe.egg:eggs
eggs = python-ldap
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.

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}
$ bin/buildout
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.

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:

[...] 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 where
(>>=) :: m a -> (a -> m b) -> m b
return a :: m a
So, 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.

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.


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


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:
$ sudo easy_install zc-buildout
En 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 bootstrap
$ wget '*checkout*/buildout-website/trunk/'

# Y lo guardamos en un lugar seguro para
a cada poyecto que use buildout
$ mv ~/bin

Ok, ya tenemos instalado buildout y bootstrap, ahora solo necesitamos un proyecto en python:
# Creamos nuestro nuevo proyecto
$ mkdir miproyecto
$ cd miproyecto

# Inicializamos buildout
$ buildout init

# ...y copiamos para
# beneficio de nuestros usuarios

$ cp ~/bin/ .

Esto creará los siguientes objetos en nuestro directorio:
En bin/ esta una copia de buildout. Aquí se instalarán los comandos de todos los huevos (paquetes en python) que instalemos.

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..

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.
Desde luego, bfg también tiene sus inconvenientes respecto a zope. Y como suele suceder, algunas de sus características positivas tienen su lado negativo:
  • 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.
En definitiva, repoze.bfg dará que hablar. Este framework tiene el potencial para desarrollar el sucesor de Plone.

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:
data Lista a = Nulo | Cons a (Lista a)

lista1 = Nulo

lista2 = Cons 3 Nulo

lista3 = Cons 7.3 (Cons 4.0 Nulo)
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).

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 = 0
longitud Cons a b = 1 + longitud b
Estas 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).

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) = a
elem i (Cons a b) = elem (i-1) b
En este caso, solo aplicamos la primera expresión cuando pidamos el elemento cero de una lista.

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:
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.

En Haskell, el código anterior sería:
data Diasemana = Domingo | Lunes | Martes | Miercoles
    | Jueves | Viernes | Sabado
la 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.

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 {
    char nombre[20];
    char apellido[20];
    int edad; } persona;
en Haskell sería:
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;
typedef struct {float modulo, angulo; } polar;
union {
    cartesiano c;
    polar p;
} complejo;
En Haskell:
data Complejo = Cartesiano Float Float | Polar Float Float
Como 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.

Wednesday, December 30, 2009

Tipos polimorficos en Haskell

Una vez pasado el shock inicial, la información sobre los tipos de las expresiones en haskell pueden ser realmente útiles. Si revisas una librería en haskell y ves la firma de las funciones, prácticamente sabrás todo lo que necesitas conocer sobre las mismas.

Este hecho siempre me sorprendió. Ver la firma de una función en C no ofrece la misma cantidad de información, incluso para alguien como yo, más familiarizado con C que con Haskell. Y desde luego, los lenguajes de script no ofrecen ninguna garantía sobre los parametros que reciben sus funciones.

Pero, por que? Como es que la firma de una función en Haskell ofrece tanta información? No entendía la respuesta hasta leer Real World Haskell. Por ejemplo, la siguiente declaración:

qsort :: Ord a => [a] -> [a]

Nos dice que qsort es una función polimorfica que toma una lista de elementos del tipo a y devuelve una lista del mismo tipo. El único dato que conocemos sobre el tipo a es que es de la clase Ord, es decir que sobre él están definidas las comparaciones de igualdad, mayor que, menor que, etc.

Lo interesante del caso, es que cuando digo conocemos, me refiero no solo a las personas que leen el código, sino también a la función. La función qsort no sabe algo sobre a que nosotros ignoremos. Cualquier función definida sobre a distinta de la clase Ord es inaccesible a qsort. Es decir, sea lo que sea que qsort haga, solo puede comparar los elementos de [a]. Y dado que las funciones en Haskell son puras, no tiene acceso a variables externas, por lo que la lista resultante depende solo de la lista de entrada.

Esto reduce enormemente las posibilidades de qsort. Sentido común en la elección del nombre por parte del autor y una linea de documentación nos dirá el resto de lo que necesitamos saber: qsort devuelve una lista con los elementos de su argumento ordenados.

Otro ejemplo es el siguiente:

mistery :: (a -> b) -> [a] -> [b]

El nombre de la función ha sido ofuscado deliberadamente. Para quienes no conocen Haskell, el primer argumento (a -> b) es una función que toma argumentos de tipo a y devuelve elementos del tipo b. De nuevo, como no sabemos absolutamente nada sobre los tipos a y b, mistery no puede usar otra información más que la dada para llevar a cabo su cometido.

Puedes adivinar que hace mistery?