Perezosidad

Algo que me ha sorprendido mucho de haskell y, que aún me cuesta mucho lidiar con ello, es el hecho de que sea perezoso.

¿Y eso qué significa?

Significa que no va a ejecutar las cosas sin más, sino que se esperará hasta que no le quede más remedio.

Por ejemplo, si tenemos algo como esto:

Como se puede ver, se “crea” una lista de infinitos elementos que empiezan en 1 (intinite_list). Como no accedemos a ningún elemento, el compilador en realidad no crea nada en memoria.

Luego indicamos que queremos elevar todos los elementos al cuadra para después quedarnos con aquellos que no son múltiplos de dos ni de tres. Eso lo almacenamos en values. En haskell esto significa que values representa esa operación, no que contenga dichos valores, así que aquí tampoco ha hecho nada: no ha elevado los infinitos elementos al cuadrado ni ha filtrado después.

Lo último que hacemos es un take 10. En este momento (si utilizamos ghci), en este momento intenta mostrar los 10 primeros elementos, lo que fuerza a hacer todo lo anterior. ¡Pero sólo lo hace sobre los 10 primeros! Ni elevamos la lista entera al cuadro, ni comprobamos los infinitos elementos. Sólo vamos a hacer esto las veces justas.

Esto lo hace todo el compilador por su cuenta.

¿A dónde quieres ir a parar?

Pues estaba hace poco mirando documentación de Rust y me he encontrado con algo muy parecido:

Básicamente hace lo miso y me sorprendió mucho porque Rust no es un lenguaje que haga evaluación perezosa.

¿Entonces cómo lo hace?

Pues simulando la evaluación perezosa. Para ello lo que hace es jugar con iteradores personalizados. Cada iterador tiene información de qué es lo que va a hacer cuando se llame a su función next. Así que cuando llamamos a map lo que hace es crear en memoria la información necesaria para hacer la operación, pero no la ejecuta. Lo mismo sucede con los filtros. Cuando llamamos al next de iterador resultante, este ejecuta el next del iterador que contiene y luego comprueba o ejecuta la condición que tenga definida.

Conclusión

Sinceramente me ha encantado ver esta aproximación. Es una modo fantástico de lidiar con el problema de infinito de un modo sencillo. Esto permite tratar muchas cosas como iteradores que, aún dando listas potencialmente infinitas, no tengan un gran impacto en rendimiento (o sí, todo dependen de como esté diseñado).

Si quieres saber más sobre evaluación perezosa quizá te interese ver la entrada en Wikipedia.

Aprendiendo haskell

Ya hace unos meses que, con tiempo que dispongo, estoy aprendiendo haskell. El motivo principal es que quería empezar con un lenguaje que fuera completamente distinto a los que ya conozco.

Confieso que de momento me parece todo un galimatías. Si bien llego a resolver algunos problemas sencillos, aún me pierdo con cosas medianamente complejas.

Además, para ir aprendiendo e ir estrujándome los sesos utilizo codewars para ir resolviendo ejercicios. Creo que es un buen modo para ir tomando contacto con el lenguaje.

¿Qué me aporta haskell?

La verdad es que no lo estoy estudiando porque sí. El objetivo es poder aprender nuevos mecanismos para afrontar los problemas. Haskell te obliga a afrontarlos desde una perspectiva completamente diferente a lo que es habitual en los lenguajes imperativos. Con ello espero que a futuro pueda incorporar nuevas formas para resolver problemas a mi día a día (siempre que valgan la pena).

¿Por qué haskell?

Porque al ser un lenguaje funcional puro me obliga a desconectar totalmente de lo que ya sé. Hay otros lenguajes funcionales, pero no llegan a la pureza de haskell¹ lo que me podría permitir desviarme de la intención de aprender programación funcional.

¿Qué haré con él?

Cuando tenga más claro como funciona y no me pierda tanto me pondré con yesod, un framework para desarrollo web. Creo que será toda una experiencia.

¿Y después?

Una vez que ya tenga haskell dominado espero poder ponerme con Rust. La verdad es que de un tiempo a esta parte ando muy desencantado con C. Cada día me parece peor lenguaje de programación, pero eso ya será otro artículo.

Notas

  1. Probablemente haya más lenguajes funcionales puros, pero haskell es de los que tiene la comunidad más amplia y, por tanto, en la que es más fácil encontrar documentación.

La necesidad de invertir en herramientas

Supongo que de una vez os habrá  pasado que ante ciertas tareas (tanto de desarrollo como de administración) repetís una y otra vez las mismas acciones o el mismo código. Otras veces, simplemente os encontráis que hay cosas que son fáciles de automatizar, pero no se hace (por ejemplo, rellenar cierto tipo de fichero en donde hay patrones muy comunes).

En este punto surge un gran problema: no se quiere automatizar/desarrollar herramientas que faciliten dicha tarea. Sí, suena extraño y contraintuitivo, pero es así. Las razones pueden ser muchas:

  • Sería otra herramienta a mantener y no hay recursos para ello.
  • Si el que hace la herramienta se va quién lo va a mantener.
  • Todo trabajo que no sea directamente la tarea asignada es tirar el dinero.
  • No lo hacemos porque no nos lo van a agradecer (sí, no siempre es cosa de la empresa no hacerlo).

Seguro que hay más motivos, pero los anteriores los he oído más de una vez.

Es muy importante tener en cuenta que todo el tiempo que se utilice en automatizar (o simplificar) este tipo de tareas debe ser considerado inversión. Y es así porque se pueden llegar a ahorrar muchas horas de trabajo (pude hablarse, en algunos casos, de hasta cientos de horas anuales) y, por tanto, el retorno de la inversión será muy importante.

Como siempre, hay que ir con cuidado: para una tarea que se hace para un proyecto concreto que sabemos que no durará más de X tiempo puede que no compense la automatización. Por tanto hay que estudiar cada caso. Pero lo que está claro es que es necesario tener esto en cuenta.

En mi caso concreto, que estoy en desarrollo, veo como poco a poco se mejora el proceso de creación de software. Por una parte podemos escribir código menos propenso a errores, y, por otra parte, tenemos más tiempo para realizar las pruebas y ofrecer un mejor producto.

Control de errores

Uno de los grandes problemas al programar es realizar el control de los errores. Por diversos motivos me ha tocado hacer un trabajo de estudio sobre qué opciones tenemos para realizar el control de los errores de una aplicación.

La verdad es que hubiese estado muy bien hacer una tormenta de ideas al respecto, pero en estos momentos estamos todos demasiado atareados.

El siguiente texto, sin pretender ser una guía exhaustiva de lo que es el control de errores, sí busca poner un punto de partida para todo aquel que quiera ver qué mecanismos hay para este fin.

Sigue leyendo Control de errores

Tratamiento de cadenas en C

Cuando empezamos a programar en C una de las cosas que probablemente más nos cuesta controlar es el tratamiento de cadenas.

El lenguaje C es de bastante bajo nivel, por lo que sus tipos de datos básicos son muy cercanos a lo que la máquina puede utilizar. Los ordenadores no entienden de cadenas, pero sí de direcciones de memoria. De hecho, no existe un tipo de cadena, sino que se usan arrays de bytes. Y lo mejor de todo, ¡en C es el programador el que debe conocer la longitud del array! (cuanto bien, y a la vez cuánto mal, han hecho los lenguajes modernos).

Todas las funciones del estándar de C presuponen que una cadena es un array de bytes donde el último carácter es un nulo.

No voy a entrar en más detalles sobre que son las cadenas. Si quieres aprender cómo se programa con ellas puedes leer Cadenas de caracteres del manual Programación en C de  wikibooks.

Quien más y quien menos habrá visto que muchos problemas de seguridad en bibliotecas en C viene precisamente del tratamiento de cadenas. Casi se podría decir que si tienes que hacer un programa que tenga que manejar cadenas es aconsejable que mires cualquier otra cosa que no sea C. Aún así, esto no siempre será posible.

En el lugar en el que trabajo hemos empezado a cambiar el modo en que utilizamos las cadenas. Para facilitar su uso y reducir la cantidad de errores que pueden producirse, hemos creado un nuevo tipo de cadena. Bueno, se parece más a un StringBuilder de java. Las motivaciones son varias:

  • Queremos un tratamiento de cadenas más rápido (tener que recorrer una cadena cada vez que quieres conocer su longitud no es óptimo).
  • Queremos poder trabajar con cadenas sin preocuparnos por reasignar constantemente memoria. ¿Habéis probado un strcat cuando estás usando arrays y el buffer de destino es más pequeño que la cadena a concatenar?
  • Para ciertas operatorias queremos poder tener cadenas con caracteres nulos. Las cadenas de C no se llevan bien con esto, pero nosotros creamos cadenas para mandar a dispositivos que no sólo aceptan el nulo, sino que ciertas operaciones lo requieren. Con las funciones estándar sería muy complicado esto.

Personalmente no creo que seamos los primeros en hacer algo así; no tengo la más mínima duda de que hay muchas personas con los mismos problemas.

Las ventajas de usar esta biblioteca son inmediatas:

  • Simplificamos el código por no tener que preocuparnos en si hay o no espacio para almacenar la cadena y en la gestión de este espacio.
  • Minimizamos errores pues no nos pasaremos nunca del buffer (siempre podemos quedarnos sin memoria).
  • Agilizamos el desarrollo al poder escribir directamente código que haga lo que queremos (es una consecuencia del primer punto).
  • Se pueden optimizar ciertas operaciones. Esto se debe a que no hay que estar constantemente recorriendo cadenas para medirlas o que al “liberar” una cadena, no hacemos free y malloc, sino que marcamos la cadena como de longitud 0 (que es más rápido y permite aprovechar la memoria).

¡Mucho ojo con esto! Que en el día a día de mi trabajo esta haya sido una buena solución no significa que lo sea para todos los casos. Siempre hay que evaluar convenientemente cada situación antes de decantarse por una u otra solución.

Pseudo lambdas con C

Últimamente se lleva mucho el tema de las funciones lambda y los closures en los lenguajes modernos. Un ejemplo es que en C++, desde la versión de 2011, ya las incorpora. Igualmente, otros lenguajes, como Ruby, Groovy o Rust ya han sido diseñados con esta característica.

Advertencia: el código que vamos a mostrar a continuación hace uso de extensiones de GNU C. Para ser exactos, hace uso de Statement Expressions y de Nested Functions. Estas extensiones solo son soportadas por unos pocos compiladores a parte de GNU C (ver Compiler support of GNU Statement Expressions en stackoverflow).

Aunque en C no existe nada parecido es factible simularlo. Todo sea dicho, el código puede ser muy feo. En el siguiente ejemplo tenemos una función que recorre un conjunto de elementos. Esta función la llamamos proccess. Por cada elemento procesado llama a una función de callback. Lo normal es crear la función de callback a parte, lo que hace que en ciertos casos sea incómodo el mantenimiento del código.

Otra opción es declarar la función de callback in situ justo en el momento que la necesitamos:

Como puede verse, nos aprovechamos de ‘({‘ y ‘})’ para declarar un bloque de código que devuelve el valor de la última expresión para crear nuestra función de callback. Como en este caso el nombre no nos importa, la llamamos ‘_’. Lo mejor de todo es que podemos repetir este nombre sin que por ello suponga que vaya a haber conflicto en la compilación ni en la ejecución.

Actualización 1: incluimos advertencia sobre compiladores.

Agilismo mal aplicado

Recientemente una gran empresa española ha decidido dar el salto a desarrollos ágiles. Esto viene motivado por un gran proyecto que abarca una gran cantidad de áreas dentro de la empresa.

Para tener claro lo que es el agilismo, estas son los pilares sobre los que se va a apoyar la empresa para usar las metodologías ágiles:

  • La planificación es lo más importante y no puede ser cambiada de ningún modo.
  • El cliente/usuario es lo menos importante y hay que interactuar lo mínimo con él.
  • La documentación debe ser muy exhaustiva.

Hay que añadir, además, que se ha retroplanificado: primero se ha definido la fecha de fin de proyecto y a partir de ahí se planifica el resto del proyecto.

Comparemos esto con el manifiesto ágil (extraído del manifiesto por el desarrollo ágil de software):

  • Individuos e interacciones sobre procesos y herramientas
  • Software funcionando sobre documentación extensiva
  • Colaboración con el cliente sobre negociación contractual
  • Respuesta ante el cambio sobre seguir un plan

Ahora comparen; cualquier parecido es pura coincidencia. Prefiero no entrar en más detalles porque el asunto me enoja mucho (por más motivos que quizá en un futuro lejano cuente).

Trabajos pendientes y futuros

Bueno, hay que ir dando continuidad al blog. Para empezar, tengo desde hace más de un año la segunda parte del artículo de creación de paquetes… ¡Vergüenza me da! Tengo que revisarlo bien antes de publicarlo y ponerme con la tercera parte.

Por otra parte, estoy tratando de retomar el mundo del desarrollo web. Vuelvo a practicar con Grails. En un futuro me gustaría ponerme con nodejs.

Y si dicen que en la vida hay que escribir un libro yo me conformo con poder hacer mi propio lenguaje de programación.

Finalmente, tengo que echarle un ojo al tema de la nueva basura hecha por el gobierno y ver si lo que dicen en Kriptópolis es factible.

Concurso de C “a pelo”

Recientemente surgió en una newsletter de O’Reilly un concurso que me pareció bastante curioso. Las bases eran las siguientes:

It seems like a new language is spawned every day, but the old tried-and-true warriors of the code world continue to soldier on. This week, the specification for C++ version 14 was frozen, and it includes such features as enhanced lambda expressions (because all the cool languages are doing it). In honor of this momentous event, we’re running a little contest to test your chops with the bare C. Sure, anyone can create awesome code using all the libraries that modern operating systems come with, but what can you do close to the metal?

Your challenge is to create a nontrivially useful application that will compile on a Linux system using the following command:

 gcc -nostdlib -o sample sample.c startstub.S

startstub.S is an i386 assembler file that can call main() without the standard libraries and can be downloaded here. You need to have a function main defined as “int main()” that can return a value, which will end up in $?. Only C is allowed, no assembler or other funny business. Entries will be judged on two criteria: binary size and functionality of the program. For reference, a program that does nothing weighs in at 1502 bytes.

Básicamente dice que hay que hacer un programa en C, que no va a poder enlazar con biblioteca de funciones alguna (no hay libc) y que no se puede hacer uso de ensamblador. Esto último es importante, porque con esta restricción se pierde la capacidad de hacer llamadas al sistema: no se puede reservar memoria con malloc, ni leer ni escribir en disco, etc. Eso sí, el programa debe tener alguna utilidad y te dicen que puede devolver un valor que será podrá ser consultado con $? (variable de la shell con el resultado del último programa ejecutado). Eso sí, el startstub.S que suministran no permite dicho uso (¿despiste quizá?).

Estas premisas tan restrictivas me animaron a tratar de crear algo para el concurso. Tras varias ideas descartadas decidí hacer una aplicación que devolviese la posición de una palabra en una lista de palabras (no parece muy útil) y que con una opción permitiese devolver la palabra de la lista más parecida a la buscada (puede resultar algo más útil).

El problema es que con lo suministrado por la gente de O’Reilly, la función main no recibe argumentos. Así que no tenemos un mecanismo sencillo para comprobar los parámetros.

La solución al problema anterior es tener en cuenta que Linux, cuando ejecuta un programa y pasa le pasa el control deja en la cima de la pila argc, luego argv y después env. Como la función _start suministrada no mueve los punteros de la pila, main tendrá esos mismos datos. Así que sólo hay que buscar un mecanismo para obtener un puntero a la cima de la pila.

En la arquitectura i386, los argumentos de las funciones se pasan por pila. Así, si tenemos una función que sólo acepta un parámetro, en el momento en que entramos en la función nos encontraremos esto en la cima de la pila:

0x000000 Dirección de Retorno
0x000004 Parámetro

Por tanto, la dirección de memoria del primer parámetro será una posición en la pila. En este caso sabemos que se correspondería con argc.

El problema es que en las arquitecturas de 64 bits, no se comporta de este modo: los 6 primeros parámetros son registros de procesador. En este caso, si es una arquitectura de este tipo, creamos una función que acepta 7 parámetros. Los seis primeros que serán registros (y los ignoramos) y el séptimo que ya se encuentra en la pila.

El resto del programa ya no tiene más misterios y lo podéis ver a continuación: