Elucubrando

Enero 28, 2008

La inferencia de tipos es tu amiga

Archivado en: — rodrigo @ 6:18 pm

Supongan que quieren hacer una estructura de datos para guardar una asignación de roles a permisos. Como lo van a usar para visualizar y editar, no basta con asociar a cada rol un conjunto de permisos, sino que necesitan asignar un booleano a cada par (rol, permiso) (Así, el código de visualización no tiene más que dibujar la matriz).

Como no estamos escribiendo C, lo que vamos a hacer es usar un Map (o hash table, o arreglo asociativo, o como le llamen en su lenguaje favorito) anidado. Vamos a tener un arreglo rolPermiso indexado por roles. La entrada asociada a cada rol es a su vez otro arreglo. Este segundo está indexado por permisos, y cada entrada es un booleano que nos dice si el rol tiene este permiso. ¿Limpio, no?

Dicho en perl, es algo parecido a

 $rolPermiso{$rol}{$permiso}

Muy bonito. Pero propenso a errores, por que tengo que tener mucho cuidado que $rol y $permiso tengan siempre algo válido. Si no, perl alegremente creará nuevas entradas en el arreglo con lo que sea que tengan esas variables.

De ese problema quien nos salva es el tipado estático. Si le avisamos al compilador que nuestro arreglo debe estar indexado por Roles, que el contenido deben ser arreglos de Booleanos indexados por Permisos, y creamos un par de enumeraciones para los Roles y los Permisos, el compilador se encarga de avisarnos «¡Oye, “Adninistrador” no es un rol válido!»

Claro que, para eso, necesitamos un sistema de tipos capaz de expresar ese párrafo. Y que al mismo tiempo no nos obligue a tonterías como el sistema de tipos de Pascal, que consideraba tipos diferentes a los arreglos de 4 enteros y a los de 5, y nos obligaba a escribir una función para ordenar arreglos de 4 enteros y otra para los de 5.

La solución, por supuesto, es el uso de tipos polimórficos (paramétricos, les llaman algunos). En Java 5 las funciones que toman arreglos asociativos genericos usan el tipo Map<K, V> en dónde K y V son variables de tipo, que expresan que a dichas funciones no les importa qué hay en el arreglo, sino sólo que la estructura es de arreglo.

La declaración de la variable que nos importa es entonces

 Map<Rol, Map<Permiso, Boolean>>

Hasta aquí, todo bonito. El problema, por supuesto, es en cuanto queremos inicializar dicha variable, usando una implementación específica de la interfaz Map y un wrapper que inicialize los valores de forma automática en el primer acceso. Como es necesario en cada punto decirle al compilador los tipos de todas las variables en cuestión la inicialización termina siendo el siguiente mounstruo:

    public Map<Rol, Map<Permiso, Boolean>> rolPermiso =
            new DefaultValueMap<Rol, Map<Permiso, Boolean>>(
                    new HashMap<Rol, Map<Permiso, Boolean>>(),
                    new DefaultValueMap.DefaultCreator<Rol, Map<Permiso, Boolean>>() {
                        public Map<Permiso, Boolean> create(Rol rol) {
                            EnumMap<Permiso, Boolean> tmp =
                                new EnumMap<Permiso, Boolean>(Permiso.class);
                            for (Permiso p : Permiso.values())
                                tmp.put(p, rol.tienePermiso(p));
                            return tmp;
                        }
                    });

(Con el problema exacerbado por que Java no tiene funciones de primer orden, así que el inicializador default tiene que quedar envuelto en un objeto de una clase anónima cuyo único propósito es pasar a su único método de un lado a otro. Fuchi.)

Una vez que se les pase el dolor de cabeza de tratar de leer eso, fijense que más o menos la mitad de esa inicialización consiste en reiterarle los tipos al compilador. Lo interesante es que el compilador sabe cuales deberían ser esos tipos (por que si los ponen mal, emite un error). Lenguajes como Haskell y ML se aprovechan de eso (y de un poco más de cosas, claro) y proporcionan inferencia de tipos. Basta con que le digan al compilador algunos de los tipos y él mismo averiguará cuales son todos los demás.

En nuestro caso, en Haskell bastaría con decir que
rolPermiso :: Map(Rol, Map(Permiso, Bool)

y luego seguirse usandolo casi igual que como lo haríamos en perl. El compilador inferirá entonces que si le pido

rolPermiso rol

“rol” debe ser una variable de tipo “Rol”, y si algúna parte del código la usé con un tipo incompatible (si le intenté asignar “Adninistrador”, por ejemplo) protestará ruidosamente por la inconsistencia, en lugar de fallar de formas misteriosas en algúna otra parte del código.

Claro que todo esto no es mucho consuelo cuando se ven obligados a usar Java, pero bueno.

Deje un comentario

Para evitar el spam, todos los comentarios deben esperar a ser aprobados. Prometo no censurar nada.

Además, cualquier comentario que diga 'poker' o 'casino', será borrado automáticamente sin aviso previo. No usen esas palabras aquí, por favor.

Gestionado con WordPress