lunes, 21 de marzo de 2011

Video: Ruby Meetup en Buenos Aires

Ruby Argentina

Como comenté en un post hace poco, la semana pasada se realizó la 7ma Ruby Meetup en Buenos Aires, organizada por la comunidad Ruby Argentina.

Como las anteriores, se hizo gracias a la colaboración de Urban Station, en Palermo, que presta el espacio, y organizada principalmente por Ernesto Tagwerker y Nacho Carrera.

Pude asistir y me tocó presentar una de las charlas relámpago, pero sobre todo pude conocer a mucha gente interesante del ambiente, que espero ver más seguido.

También presentó Michel Martens su microframework para desarrollo web llamado Cuba, que tiene similaridades con Sinatra pero es más rápido y liviano todavía. Sumamente recomendable, y espero publicar pronto el video en un post aparte.

También tuve la oportunidad de grabar un poco de la reunión, y comparto el segmento de introducción donde pueden ver a Ernesto y Nacho, y luego la presentación de Ernesto sobre cómo colaborar en proyectos de código abierto utilizando GitHub. Es una recorrida muy breve pero interesante, aunque pido disculpas de antemano porque la cámara a veces debería haber tomado más directamente la pantalla. No es fundamental, de todas maneras, ver el código del ejemplo, sino entender la explicación.

Dejo también dentro del canal de Code & Beyond videos de reuniones anteriores subidos por Nacho, con charlas más extensas.

jueves, 17 de marzo de 2011

El juego de la vida en HTML5 usando Canvas

El juego de la vida de Conway (también conocido simplemente como "Life") es un ejemplo clásicos de Autómatas Celulares creado por John Horton Conway en los 70.

Consiste en una grilla de puntos (el universo) donde cada punto puede contener un individuo o célula (un punto de la grilla que está encendido o vivo (los puntos tienen estado binario: vivos o muertos).

El juego funciona sólo (se lo conoce como un juego de cero jugadores), y lo único que se puede hacer es preparar el estado inicial y luego echarlo a correr. La corrida involucra generaciones, o pasadas por la grilla completa para analizar el estado y calcular el de la siguiente pasada. El cálculo se hace analizando para cada punto su estado y el de los ocho que lo rodean (sus vecinos):

Celula y Vecinos

Para determinar si la célula analizada vive o muere, se aplican las siguientes reglas:

  • Una célula viva con menos de dos vecinos vivos se muere (de soledad)
  • Una con dos o tres vecinos vivos sobrevive
  • Una con más de tres vecinos vivos se muere (por sobrepoblación)
  • Una célula muerta con exactamente tres vecinos vivos, nace (por reproducción)

La grilla debe considerarse como un toroide, o sea que los puntos de la última fila continúan en la superior, y los de la última columna derecha continúan en la primera de la derecha y viceversa.

Parece sencillo, pero ha sido estudiado durante todos estos años no solamente como un interesante Code Kata, sino como un interesante ejercicio de simulación de ecosistemas.

Como no hay mejor manera de entenderlo que verlo en acción, les recomiendo ver esta versión implementada en HTML 5, utilizando el tag Canvas.

Cosas interesantes que ocurren con este juego es que espontáneamente aparecen configuraciones estables (que permanecen constantes durante muchas generaciones, variando entre un número determinado de estados. Es un interesante ejemplo de comportamiento emergente, y aunque parezca un oximorón, un ejemplo sencillo de sistemas complejos.

Juego de la Vida

Por supuesto, una de las cosas muy interesantes de ver es código fuente. Son sólo 234 líneas de JavaScript y aunque parece un poco largo para un post, creo que vale la pena pegarlo completo, aunque recomiendo que si alguien lo quiere tocar baje siempre la última versión disponible en GitHub.

 

/*
 * Copyright 2011 Julian Pulgarin 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var Point = function(x, y) {
    this.x = x;
    this.y = y;
};

var graphics = function() {
    var canvas;
    var ctx;
    var canvasId;

    var cellSize = 10; // pixels
    var onColour = 'rgb(0, 200, 0)';
    var offColour = 'rgb(200, 0, 0)';
    var gridColour = 'rgb(50, 50, 50)';

    var initCanvas = function(canvasId) {
        this.canvas = $(canvasId).get(0);
        this.ctx = this.canvas.getContext('2d'); 
        this.canvasId = canvasId;
    }

    var drawCell = function(x, y, alive) {
        var g = graphics;
        g.ctx.fillStyle = (alive)? onColour : offColour;
        g.ctx.fillRect(x * cellSize + 1, y * cellSize + 1, cellSize - 1, cellSize - 1);
    }

    var handleMouse = function(e) {
        var l = life;
        var g = graphics;
        var that = this;
        var cell = getCellPointUnderMouse(e);
        var state;
        processCell(cell);
        $(g.canvasId).mousemove(function(e) {
            cell = getCellPointUnderMouse(e);
            processCell(cell);
        });
        function getCellPointUnderMouse(e) {
            return new Point((e.pageX - that.offsetLeft) / g.cellSize | 0, ((e.pageY - that.offsetTop) / g.cellSize) | 0);
        }
        function processCell(cell) {
            var x = cell.x;
            var y = cell.y;
            if (x > l.xCells - 1 || y > l.yCells - 1) {
                return;
            }
            if (typeof state == 'undefined')
            {
                state = !l.prev[x][y];
            } 
            l.prev[x][y] = state;
            drawCell(x, y, state);
            // TODO: Consider setting next as well
        }
    }

    function paint() {
        var g = graphics;
        var l = life;

        for (var x = 0; x < l.xCells; x++) {
            for (var y = 0; y < l.yCells; y++) {
                g.drawCell(x, y, l.prev[x][y]);
            }
        }
    }

    return {
        canvas: canvas,
        ctx: ctx,
        canvasId: canvasId,
        cellSize: cellSize,
        onColour: onColour,
        offColour: offColour,
        gridColour: gridColour,
        initCanvas: initCanvas,
        drawCell: drawCell,
        handleMouse: handleMouse,
        paint: paint,
    }
}(); 

var life = function() { 

    var yCells; 
    var xCells;
    var prev = []; // previous generation
    var next = []; // next generation

    var _timeout;
    var _alive = false;

    var initUniverse = function(canvasId) {
        var l = life;
        var g = graphics;
        g.initCanvas(canvasId);
        l.xCells = ((g.canvas.width - 1) / g.cellSize) | 0;
        l.yCells = ((g.canvas.height - 1) / g.cellSize) | 0; 
        g.ctx.fillStyle = g.offColour;
        g.ctx.fillRect(0, 0, l.xCells * g.cellSize, l.yCells * g.cellSize);
        g.ctx.fillStyle = g.gridColour;

        for (var x = 0; x < l.xCells; x++) {
            l.prev[x] = [];
            l.next[x] = [];
            g.ctx.fillRect(x * g.cellSize, 0, 1, l.yCells * g.cellSize);
            for(var y = 0; y < l.yCells; y++)
            {
                l.prev[x][y] = false;
            }
        }
        g.ctx.fillRect(l.xCells * g.cellSize, 0, 1, l.yCells * g.cellSize);
        for(var y = 0; y < l.yCells; y++)
        {
            g.ctx.fillRect(0, y * g.cellSize, l.xCells * g.cellSize, 1);
        }
        g.ctx.fillRect(0, l.yCells * g.cellSize, l.xCells * g.cellSize, 1);
        $(canvasId).mousedown(g.handleMouse);
        $('body').mouseup(function(e)
        {
            $(g.canvasId).unbind('mousemove');
        });
    }

    var nextGen = function() {
        var l = life;
        var g = graphics;

        for (var x = 0; x < l.xCells; x++) {
            for (var y = 0; y < l.yCells; y++) {
                l.next[x][y] = l.prev[x][y];
            }
        }

        for (var x = 0; x < l.xCells; x++) {
            for (var y = 0; y < l.yCells; y++) {
                count = _neighbourCount(x, y);

                // Game of Life rules
                if (prev[x][y]) {
                    if (count < 2 || count > 3) {
                        next[x][y] = false;
                    }
                } else if (count == 3) {
                    next[x][y] = true;
                } 
            }
        }

        for (var x = 0; x < l.xCells; x++) {
            for (var y = 0; y < l.yCells; y++) {
                l.prev[x][y] = l.next[x][y];
            }
        }

        g.paint();
    }

    var toggleLife = function() {
        var l = life;

        if (!l._alive) {
            l._alive = true;
            l._timeout = setInterval("life.nextGen()", 100);
        } else {
            l._alive = false;
            clearInterval(l._timeout);
        }
    }

    var clear = function() {
        var l = life;
        var g = graphics;

        for (var x = 0; x < l.xCells; x++) {
            for (var y = 0; y < l.yCells; y++) {
                l.prev[x][y] = false;
            }
        }
        g.paint();
    }

    var _neighbourCount = function(x, y) {
        var l = life;
        var count = 0;
        var neighbours = [
            l.prev[x][(y - 1 + l.yCells) % l.yCells],
            l.prev[(x + 1 + l.xCells) % l.xCells][(y - 1 + l.yCells) % l.yCells],
            l.prev[(x + 1 + l.xCells) % l.xCells][y],
            l.prev[(x + 1 + l.xCells) % l.xCells][(y + 1 + l.yCells) % l.yCells],
            l.prev[x][(y + 1 + l.yCells) % l.yCells],
            l.prev[(x - 1 + l.xCells) % l.xCells][(y + 1 + l.yCells) % l.yCells],
            l.prev[(x - 1 + l.xCells) % l.xCells][y],
            l.prev[(x - 1 + l.xCells) % l.xCells][(y - 1 + l.yCells) % l.yCells],
        ];

        for (var i = 0; i < neighbours.length; i++) {
            if (neighbours[i]) {
                count++;
            }
        }
             
        return count;
    }

    return {
        yCells: yCells,
        xCells: xCells,
        prev: prev,
        next: next,
        initUniverse: initUniverse,
        nextGen: nextGen,
        toggleLife: toggleLife,
        clear: clear,
    }
}();

miércoles, 16 de marzo de 2011

En el jardín del buen y del mal JavaScript

Better JS Docs

Dos especialmente dedicados miembros de la comunidad de desarrolladores StackOverflow, Ivo Wetzel (23 años, trabajando en Zynga Alemania) y Yi Jiang (17 años, estudiante de Singapur) , han construido el sitio JavaScript Garden, dedicado a corregir errores de interpretación y mejorar el entendimiento sobre este fantástico lenguaje.

Además de ser un ejemplo de sobriedad y buen diseño (noten la fluidez de la navegación desde el menú a la derecha), me parece un gran trabajo de difusión, cubriendo por áreas las particularidades de JavaScript que suelen confundir a algunos programadores proveniendo de entornos bastante diferentes.

Algunos ejemplos que tomo del sitio:

Objetos y propiedades

En JS todo es u objeto, excepto null y endefined.

false.toString() // 'false'
[1, 2, 3].toString(); // '1,2,3'

function Foo(){}
Foo.bar = 1;
Foo.bar; // 1 

Es común pensar que los números literales no pueden tratarse como objetos porque no se puede acceder a sus miembros de la manera tradicional, pero esto es un defecto histórico del parser, que puede subsanarse fácilmente utilizando doble punto o espacios, como se muestra aquí:

2.toString(); // arroja SyntaxError

2..toString(); // el segundo punto hace que se interprete bien
2 .toString(); // o se puede dejar un espacio antes
(2).toString(); // o el uso de paréntesis también

En JS los objetos están implementados como hashmaps, o listas de elementos clave-valor, con lo que es posible crear objetos utilizando esta notación:

var foo = {}; //  objeto vacío (hereda de object.prototype)

// un nuevo objeto con la propiedad 'test' y valor 12
var bar = {test: 12}; 

Y las propiedades también pueden accederse como tales o como índices. Incluso al usar la notación con corchetes se pueden soportar propiedades que no serían posibles utilizando puntos, como en:

var foo = {name: 'Mafalda'}
foo.name; // Mafalda
foo['name']; // Mafalda

var get = 'name';
foo[get]; // Mafalda

foo.1234; // SyntaxError
foo['1234']; // funciona

Prototipos

Esta es una de las facetas más "raras" para muchos programadores provenientes de una cultura de orientación a objetos "canónica. De hecho escuché varias veces el argumento de que "JavaScript no es orientado a objetos porque no tiene clases ni herencia". Esto es totalmente falaz: JS es orientado a objetos, sólo que tiene herencia por prototipos, y no por clases. En este sitio tienen estos buenos ejemplos de cómo mantener una jerarquía tradicional utilizando cadenas de prototipos.

Hay mucha más información y no es mi objetivo repetir todo el sitio, por lo que les recomiendo dedicarle una hora a leerlo y hacer algunas pruebas. No lleva mucho más que eso y para muchos ahorrará muchos dolores de cabeza, o les permitirá valorar JavaScript como el lenguaje merece.

martes, 15 de marzo de 2011

Videos de PyCon 2011 (Atlanta, USA) y PyCamp 2011 (La Falda, Córdoba) la semana próxima

Pycon

La parte principal de la conferencia internacional de Python, PyCon 2011, ya terminó, y afortunadamente podemos disfrutar de enorme cantidad de videos de las sesiones (en inglés, claro).

Pueden recorrer los videos publicados (tengan en cuenta que después de varias páginas empiezan a aparecer videos de PyCon 2010 y 2009), o ver algunos de los que seleccioné (arbitrariamente, por supuesto) en esta lista:

PyAr

Para quienes no pudieron viajara a Atlanta (imagino que la mayoría de nosotros), hay una oportunidad de pasar unos días en la hermosa localidad de La Falda, en Córdoba, Argentina, la semana que viene, y asistir al PyCamp 2011.

Como es de costumbre se puede ver la lista de gente que va a ir o está interesada (sin confirmar todavía), y la lista de temas propuestos (entiendo que se discuten y seleccionan en el momento).

Para el final, les dejo el video de esta entrevista grabada en la PyCon 2011 con Guido Van Rossum, autor de Python y "benevolente dictador de por vida" del lenguaje:

 

lunes, 14 de marzo de 2011

Probar Haskell en tu navegador

Real World HaskellSiguiendo el camino que inicio _why con TryRuby.org, Chris Done creo TryHaskell.org, una página donde encontramos una consola interactiva para probar Haskell, el lenguaje puramente funcional por excelencia.

Esta consola es del tipo REPL (read, evaluate, print loop) y permite ingresar comandos y expresiones. Como en Haskell la declaración de funciones es una expresión (utilizando let) se pueden crear y ejecutar funciones dentro del entorno.

Por supuesto, como en el caso de TryRuby, se puede seguir un tutorial que va guiando al asistente secuencialmente a través de las expresiones más sencillas, manipulación de strings, manejo de listas, declaración de símbolos, etc.

La consola interpreta y muestra los resultados parciales e informa si hay situaciones de error, como puede verse debajo.

Consola de Haskell

 

Adicionalmente a los ejemplos que la misma página va proponiendo, hay también un link al libro "Real World Haskell", de Bryan O'Sullivan, Don Stewart, y John Goerzen, editado por O'Reilly Media y disponible gratuitamente en línea (en inglés), donde se pueden encontrar muchísimos más ejemplos y por supuesto un nivel de profundidad mucho mayor en la presentación del lenguaje.

Para aprovechar a fondo la combinación entre el libro y TryHaskell basta con arrancar por el capítulo 1, donde se describen muchos ejemplos iniciales utilizando ghci, que es la consola interactiva del entorno Haskell, lo que es el equivalente directo de la consola que encontramos en la página.

Para todos aquellos interesados en la programación funcional pero que no tuvieron la oportunidad (o el coraje) de acercarse a Haskell, puede ser una buena manera de entender un poco más de que se trata sin la necesidad de tener que hacer una gran inversión inicial de tiempo.