12 octubre, 2009

CSI XVIII: Los húngaros nombran con prefijos

El reciente post acerca del nombre de nuestras futuras (o muy presentes) cerveceras dio paso, de forma natural, a pensar de qué modo llamaríamos a nuestros productos. Ya lo comenté en el blog de Chela: me gustan los nombres relacionados con la música y hay muchos donde elegir en las anotaciones de las partituras, en latín, italiano o nuestra lengua: Ad Libitum, para la cerveza tomada a la velocidad que uno quiera. Tornada (estribillo), si la cerveza es para ser tomada una y otra vez, Ritardando, para tomar despacio...

El nombre de las cosas es importante. Y, como la mayor parte de cosas importantes, suelen dar problemas. Y en informática, el nombrado de variables puede suponer todo un reto, especialmente porque cuando se hace sin cuidado puede llevar a errores de programación, cuando no a imposibilitar que cualquier otra persona entienda el código (código inmantenible).

Por cierto, una variable no es más que un espacio en memoria destinado a albergar, en algún momento durante la ejecución del programa, algún valor. Necesitamos darle nombres para hacer referencia a ese valor. Así que, ¿qué nombre les ponemos?


El caso es que no vale el primero que se nos ocurra. MaryPoppins, por ejemplo, no vale. Porque luego tienes que operar con estas variables y en el código te pueden aparecer cosas como:

PedidoOctubre = MaryPoppins[3] * OdioAMiJefe;

Eso es código inmantenible. Una risa, si no te fuera el trabajo en descifrarlo y entenderlo.

Por ello todos debemos estar de acuerdo en que el nombre de las variables debe dar pistas acerca de qué tipo de almacén estás gastando para esa variable, qué es lo que deseas guardar en ellas o qué intención tienes al emplearla. Como llamar AldolfoHitler a tu hijo: el nombre da pistas.

Las empresas serias definen una serie de reglas a la hora de escribir programas: son los coding standards (o guía de estilo). Son simples convenciones para que -entre otras cosas-, con independencia de las personas que hayan pasado por la empresa, el mismo estilo perdure. Las convenciones pueden ayudar en el problema de nombrer variables, imponiendo una serie de reglas. Algunas de ellas están muy extendidas y las emplean la mayor parte de programadores en diversos lenguajes. por ejemplo, llamar i, j, k a las variables dentro de un bucle.

Una de las Cosas que Sí se estudió en Informática es la notación húngara. La notación húngara fue creada por Charles Simonyi, uno de los grandes programadores y reciente turista espacial1. La notación húngara se caracteriza por anteponer al nombre de la variable un prefijo que nos da pistas acerca del tipo de variable que estamos empleando. El tipo de variable es la forma del almacén que estamos gastando para guardar su valor.

No voy a a explicar demasiado, porque esto da para otro post: me contentaré con poner algunos ejemplos sencillos. Una variable de tipo carácter está pensada y tiene el espacio asignado para guardar un carácter. Una variable que fuera de tipo cadena tendría un almacén asignado para almacenar una cadena de caracteres.

Entonces, si queremos almacenar un carácter en nuestro programa para guardar la preferencia de un usuario (que lo introduce mediante el teclado), declaramos una variable llamada UserOption y si empleamos la notación húngara, lo hacemos así:

caracter cUserOption;

Mediante esta linea le decimos al compilador (que es típicamente quien traduce nuestro programa a la máquina) que en algún momento tendrá que reservar un hueco en memoria para almacenar una variable de tipo caracter y que se llamará cUserOption. La 'c' usada como prefijo nos recuerda, cuando realizamos operaciones con esta variable, que se encuentra almacenada como un carácter.

La notación húngara tiene cierto sentido cuando, al operar entre variables de distinto tipo, podemos obtener problemas o resultados indeseados. Y justamente, el hecho de que se produjeran tantos errores al operar entre variables de distintos tipos dio lugar a la creación de los lenguajes fuertemente tipados, en los cuales el compilador impide realizar esta clase de operaciones. Así que la notación húngara dejó de tener sentido para muchos programadores.

Otra versión de la notación húngara sugiere que el prefijo debe estar más relacionado con la función que la variable va a cumplir que con el tipo de almacenamiento. Esta versión del invento cuenta con más adeptos, ya que no ha perdido el sentido al reforzar los compiladores el análisis de tipos. Algunos ejemplos de este uso son:

numeroentero nSandias;

Que informa al compilador de que debe guardar un espacio para almacenar un número entero. En ese espacio guardaremos el número de sandías de nuestra aplicación. De ahí el prefijo 'n'. Pero como si no hay guía de estilo, nadie tiene por qué saber que 'n' es de 'número' en vez de 'nuevas', pues resulta que la notación húngara sigue sin ser ninguna panacea.

Esta notación, sin embargo, se sigue empleando... y mucho. Porque Charles Simonyi fue fichado por Bill Gates y la notación húngara pasó a formar parte intrínseca de las aplicaciones de Microsoft, incluyendo su Sistema Operativo más popular. En los últimos tiempos, sin embargo, hasta los de Redmond se desdicen y recomiendan no emplear este sistema de nombrado en el código.

Recordar la notación húngara de prefijos me ha recordado otras notaciones algebraicas, que también tienen que ver mucho con la informática. Me refiero a la notación polaca (o infija) y a la polaca inversa (o postfija). Rápidamente:

(3 + 2) * 5 - Ésta es una operación como a usted a y mi nos enseñaron en el cole.
* + 3 2 5 --- Es lo mismo que antes en notación polaca. Lo inventó un polaco.
3 2 + 5 * --- Es lo mismo, pero en polaca inversa.


De los tres, el más útil y divertido es el de polaca inversa. Útil para algunas calculadoras, claro. Polaca y polaca inversa no precisan de paréntesis, ni de conocer las reglas de precedencia (no hay expresiones ambiguas). Eso le viene muy bien al informático para realizar cálculos. En polaca inversa, cada vez que se encuentra un operador, se realiza el cálculo indicado: los operados habrán sido leídos anteriormente (o hay un error en la expresión).

La polaca inversa tiene desventajas. Los espacios en las expresiones son de vital importancia para no confundir la expresión. Y como no se enseña en las escuelas ni se aplica sobre el papel, su uso está muy limitado.

Pero sirve para entender el chiste de arriba (cortesía de xkcd, como no). Ante ustedes, ¡un sandwitch de salchicha polaca inversa!

1 El más famoso turista espacial relacionado con la informática es Mark Shuttleworth, empresario instigador de Ubuntu.

Notación Húngara: por Charles Simoyi.
Notación Polaca (wikipedia inglesa)

Posts relacionados

22 agosto, 2009

CSI XVII: El álgebra de Boole

Demasiado tiempo hacía que no escribía acerca de las Cosas que Sí se dan en Informática. Pero mi retiro veraniego me ha despertado del letargo y me ha traído, con el viento levantino, los recuerdos de una de las primeras asignaturas que estudié en la carrera y que constituye una base esencial de la propia informática. Me refiero al Diseño Lógico. Mi idea era ir más allá y hablar de lenguajes de descripción de sistemas electrónicos, que tan olvidados e inútiles les resultan a los informáticos que se decantaron por los lenguajes de alto nivel.

Pero para entender de qué trata éste es necesario disminuir un poco las pretensiones e introducir algo todavía más elemental, que se estudia en el primer curso de la carrera y que, a menos que la especialización te conduzca por áreas de informática industrial o electrónica, se pierde con facilidad. Se trata del Álgebra de Boole, que hoy, en este capítulo de Cosas que Sí se dan en Informática, presentaremos: tanto en su definición matemática, como en su utilidad electrónica.


El Álgebra de Boole toma su nombre de George Boole, matemático y filósofo inglés del s. XIX. Sus estudios acerca de la lógica le llevaron a desarrollar un modelo matemático con el fin de analizar el razonamiento humano y de realizar operaciones con sentencias. De su trabajo surgió la estructura matemática que asigna a cada sentencia el valor de VERDADERO (o 1) y FALSO (o 0) y define las operaciones matemáticas que pueden realizarse sobre estos elementos.

Porque es eso mismo lo que conforma una estructura algebraica: un conjunto de elementos y un conjunto de operaciones definidas sobre los elementos. El álgebra de Boole tiene forma de anillo: esto significa que las operaciones definidas satisfacen ciertas propiedades. En la wikipedia pueden continuar con este tema.

Los elementos del álgebra de Boole son pues dos, que llamaremos 0 y 1. Las operaciones básicas que se pueden realizar sobre estos elementos son 3: disyunción (OR, o suma), conjunción (AND, o multiplicación) y negación (NOT, o inversión). Los dos primeros son operadores binarios, esto es, requieren dos elementos como entrada. La negación se aplica sobre un sólo operando: es unario, pues. Bien, si hasta ahora ha sido sencillo, lo que viene ahora lo es más.

Las tablas de verdad básicas definen el comportamiento de los operadores para las entradas. O lo que es lo mismo: su resultado.

OR
ABA OR B
0
0
0
0
1
1
1
0
1
1
1
1
AND
ABA AND B
0
0
0
0
1
0
1
0
0
1
1
1
NOT
ANOT A
0
1
1
0

Explicar una tabla de verdad es casi innecesario: la primera tabla describe la operación OR: 0 OR 0 da como resultado 0. En cambio, 0 OR 1, 1 OR 0 y 1 OR 1 dan como resultado 1.

Aunque no es adecuado, viene bien evaluar una sentencia disyuntiva a un valor de verdad para acabar de entender la correspondencia lógica. La frase Marta tiene un abrigo azul o tiene una chaqueta colorada es cierta (verdadera: 1) si alguna de las sentencias que la forman -tiene un abrigo azul, tiene una chaqueta colorada- es verdadera. Para que la conjunción sea cierta (Marta tiene un abrigo azul y tiene una chaqueta colorada) es necesario que las dos sentencias individuales -los operandos- sean verdaderos. De este modo se entiende la tabla de verdad de la AND.

XOR
ABA XOR B
0
0
0
0
1
1
1
0
1
1
1
0
Mediante sencillas combinaciones de los tres operadores básicos podemos obtener los operadores derivados, que nos sirven para representar otras relaciones lógicas, entre las que se encuentran la o exclusiva, la implicación o la equivalencia. Es posible que a muchos de los lectores les suene esto, ya que las tablas de verdad son las herramientas empleadas en el estudio de la lógica proposicional, que se cursa típicamente en el instituto. Los operadores derivados más frecuentes tienen sus propios símbolos. En la tabla de al lado se muestra una o exclusiva o XOR. La función XOR puede ser construida a partir de operaciones básicas, aunque no de modo sencillo. Sin embargo, resulta muy útil a los informáticos y es ampliamente empleada, como veremos enseguida.

Las representaciones de estos operandos son muy diversas y los informáticos preferimos la que emplea puertas lógicas. Y sin embargo... ¡un momento! ¿qué pintamos los informáticos aquí?. ¿Quién nos ha dado vela en este entierro?.

Pues en realidad fue Claude Shannon, uno de los grandes, quien nos dio vela. Ya hablamos de Shannon cuando describíamos el mínimo de información necesaria para codificar una cadena de bits. Pero antes de ocuparse de la teoría de la información, Shannon demostró que el álgebra de Boole se podía utilizar en el análisis y la síntesis de la conmutación y de los circuitos digitales.

Un circuito digital es un circuito electrónico que emplea señales eléctricas que representan (mediante la tensión) dos valores: 0 y 1. El circuito, visto como una caja negra consta de una serie de entradas y unas salidas. Lo que Shannon demostró es que cualquier salida puede ser producida a partir de las entradas mediante una combinación de operadores booleanos, esto es, mediante un conjunto de puertas lógicas, interconectadas de cierto modo. De este modo podemos ver cualquier transformación de entradas en salidas como la aplicación de cierta función (operación) lógica1. A esto se le llama circuito, o sistema, combinacional.

SUMADOR
ABSumaCarry
0
0
0
0
0
1
1
0
1
0
1
0
1
1
0
1
Así, por ejemplo, un circuito básico podría ser el el que suma dos bits. Recordemos que la suma de dos bits era sencilla: 0 + 0 = 0, 0 + 1 = 1, 1 + 0 = 1, 1 + 1 = 0 y me llevo 1 (acarreo). Un sumador simple (half-adder) de un bit tiene dos entradas y dos salidas (la suma y el acarreo). Lo llamamos simple para diferenciarlo del completo, que añadiría como nueva entrada el acarreo del anterior. De este modo podríamos construir sumadores de cualquier número de bits sin más que encadenar la salida del acarreo de uno a la entrada del otro. Pero nosotros nos quedaremos con el simple, para no complicar el circuito.

El sumador que vemos emplea una puerta AND (la de abajo de la imagen) y una puerta XOR, la superior, que ya presentamos anteriormente. Con las tablas de verdad de ambas puertas, es muy sencillo jugar a poner unos y ceros en las entradas y ver cómo los resultados coinciden con los de la suma.

Puede parecer que un sumador de un bit sólo sirve para jugar, pero de hecho, es la base del cálculo de todas las operaciones que realizan los computadores. Sin embargo es cierto que los ingenieros que construyen circuitos lógicos tienen herramientas para no tener que estar trabajando con miles de puertas lógicas cada vez que pretenden diseñar una nueva unidad aritmética para un computador, por poner ejemplo. Qué pinta tiene alguno de los lenguajes para expresar circuitos lógicos de manera sencilla y potente será explicado en posteriores entregas.

¡Un saludo y hasta la próxima!

1 ¿De qué manera se produce "físicamente" la transformación de dos niveles de tensión a las entradas de una puerta en un nivel de tensión a la salida que corresponda al resultado de la puerta?. Bien eso depende la tecnología empleada y sería necesario entender física de semiconductores para explicarlo. Queda fuera del alcance de este blog.

Infinidad de circuitos digitales construidos de forma incremental.
Álgebra de Boole en la wikipedia en español: se incide en la representación como como conmutación de circuitos.
La imagen del half adder es de dominio público

Posts relacionados

24 mayo, 2009

CSI XVI: los números de los computadores (II)

Si recuerdan el ya lejano último post acerca de la informática, recordarán que había motivos para estar mosqueado. Muy mosqueado, de hecho, porque vimos que los bancos nos podían chulear con facilidad la parte fraccionaria de los ahorros. Pero eso es porque todavía no les he enseñado cómo representar números reales en los computadores. Y tranquilos, que eso es precisamente lo que voy a comentar hoy.

Ustedes los humanos emplean un punto (o una coma) o notación científica para representar un número con decimales. Pero la cosa es un poco más compleja si no se pueden valer de esos artificios. Para expresar números reales en un ordenador, la solución más sencilla es convenir que una parte de la ristra de unos y ceros represente la parte entera y el restos de unos y ceros representen la parte decimal. Se dedican n bits para la parte entera y p para la fraccionaria. Por supuesto, n y p son fijos para un computador, para no hacerse un lío. Por eso este sistema se denomina de coma fija.


La coma fija mola, porque no tenemos que aprender nada nuevo (aparte de lo que aprendieron en el anterior post). Con 8 bits podemos dedicar 5 a la parte entera y otros 3 a la parte decimal, por ejemplo. De manera que si usamos Complemento a 2 para representar la primera (para la decimal no empleamos ninguna codificación especial) entonces podemos representar un rango de números que va desde -16.0 hasta +15.875. La precisión la marca la cantidad de bits en la parte fraccionaria, claro, que en este caso es de 0.125 (1/(2^3)).

La coma fija no mola, porque operaciones entre números dentro del rango de representación exceden con facilidad el propio rango. Un ejemplo decimal: 0.345 * 0,001 = 0.000345. Si gastamos 3 dígitos para la parte entera y tres para la parte decimal, es evidente que el resultado de la multiplicación con este sistema sería 0. Y nos volvemos a quedar sin céntimos en los bancos.

Para hacer esto más flexible se inventó la coma flotante. La coma flotante es un lío, pero que es muy útil. Un número real se representa como:

R = M * BE donde:
M es número fraccionario, donde se utiliza un bit para representar el signo (como en Signo y magnitud).
B es la base. 2, en casi todos los casos.
E es el exponente, que se representa en exceso Z (esto no lo he explicado).


Si lo pensamos un poco, esto no es más que una aproximación a la notación científica. Por ejemplo, -0.000456 = -456 * 10-6, donde el signo es negativo, la mantisa 456, la base 10 y el exponente -6. La complejidad de la coma flotante es ajustar cada campo a un número limitado de bits. Y para eso vamos a aprovechar hasta el máximo.

El formato IEEE754 de simple precisión (el más conocido de representación de reales) emplea un total de 32 bits: 1 para el sigo, 8 para el exponente y 24 para la mantisa.

¿Cómo?. ¿Que no le salen las cuentas?. Claro, amigo, lo que usted no sabe es que los ingenieros aprovechamos todo el espacio que podemos. Y le ganamos un bit a la mantisa de forma sencilla. Porque, si usted quiere utilizar el formato IEEE754 es porque tiene algo que representar, aunque sea muy pequeño. Un ejemplo:

0,0000101 (binario). ¿Por qué tengo que almacenar en la mantisa los cuatro ceros? Al fin y al cabo, 0,0000101 = 1,01 * 2-5. Sólo tengo que ajustar el exponente (restándole 5). Lo bueno es que esto lo puedo hacer para todos los números, de forma que, sea cual sea el número a representar, el primer bit de la mantisa será 1. Y como el primer bit de la mantisa es siempre 1, me puedo ahorrar el representarlo (¡siempre que luego me acuerde de él a la hora de interpretar el número!). De este modo tenemos 24 bits para representar la mantisa, aunque sólo empleemos 23.

A la operación de dejar un único bit significativo (1) a la izquierda de la coma se le llama normalizar la mantisa.

Hemos dicho que tras normalizar la mantisa conviene ajustar el exponente. Pero no hemos hablado de cómo se representa este mismo, ni qué significa exceso Z. Es una cosa tan tonta que da risa. En exceso Z se escoge cualquier número (Z) para representar el cero. Cualquier número entero A se expresa como el natural A+Z. Por ejemplo, en exceso 127, el -7 se expresa como 120 (-7 + 127). El 3 se expresa como 130 (3 + 127).

Precisamente 127 es el Z escogido para representar el exponente en el formato IEEE754. Por tanto, ya disponemos de todos los elementos para ponernos a calcular números reales. Buenos, nos falta dar el orden de los campos: Signo - Exponente - Mantisa. Ya está.

Representar -209.5625 en coma flotante IEEE754 (simple precisión)
S = 1 (signo negativo)
Parte entera: 209 = 11010001
Parte fraccionaria: .5625 = 0.1001 (2-1 + 2-4)
Mantisa: (parte entera.parte fraccionaria) 11010001.1001 = 1.10100011001 * 2+7
Exponente Z = 127 + 7 = 134, que en binario es 10000110

                        S E        M
De lo cual: -209.5625 = 1 10000110 10100011001000000000000


Y ya hemos aprendido a representar números reales. Por supuesto, al ser un rango de representación finito, con este formato también tenemos cierto límite en la precisión (determinado por los 24 bits de mantisa). Para los más avezados dejo su cálculo.

Yo quiero incidir en otra cosa, que no sé si habrán notado. A pesar de que hemos detallado la representación de números en distintos formatos, al final, en su computador, todos los formatos no son sino ristras de unos y ceros, de modo que la misma longaniza puede ser un número entero negativo, real, o positivo. O no ser un número. Por eso a la hora de escribir un programa es fundamental dar a conocer al computador cómo debe interpretar un valor. Esto se consigue con los tipos de datos.

Cuando queremos gastar un valor, empleamos variables para almacenarlas. Y en gran parte de lenguajes es necesario declarar el tipo de dato de una variable antes de gastarla, para que el computador sepa cómo debe operar con ella.

En C, el lenguaje más extendido, si declaramos una variable como int (entero) lo más posible es que le digamos al computador que esa variable debe representarse en Complemento a 21. Si declaramos un float es muy posible que para representar esa variable se gasten 32 bits (simple precisión) en IEEE754. Y si declaramos un unsigned char, entonces estamos gastando 8 bits en una representación binaria sin signo (positivo).

Y ahora el chiste:

Si los androides algún día sueñan con ovejas eléctricas no olvides declarar el contador de ovejas como long int.



¿Que ha pasado?. Que el contador de ovejas estaba declarado como int (16 bits). Y el rango de representación es (Ca2) -32768 (1000000 00000000) - 32767 (01111111 1111111). Al sumar uno a 32767 el número de ovejas se convierte en negativo. De haber declarado el contador como long int, posiblemente habría tenido 32 bits para representar la cuenta. Eso significa que un androide insomne habría contado hasta 2147483647 (231-1) antes de que sucediera lo mismo. Y si declarara la variable como unsigned long (sin signo), podría contar hasta 4294967295. La siguiente cuenta sería 0, ya que el computador jamás interpretaría este número como negativo.

Ojalá me disculpen. Tan sólo quería compartir humor friqui con ustedes.

1 El número de bits con los que se representa y el formato de representación dependen de la arquitectura del procesador. Por eso es un error asumir que un int tiene siempre, por ejemplo, 32 bits.

Apuntes clase. Pero en la wikipedia también viene bien explicado
Pinar me pasó el link a la tira: más humor en su web.

Posts relacionados

10 febrero, 2009

CSI XV: Los números de los computadores

Con la crisis, amigos míos, todos echamos números. Los hay que no, pero es lo que tiene vivir con dinero de los demás: atrofia el ingenio para buscarse alternativas, libres y gratuitas. Echar números es sólo necesario si vas algo apurado, como el 90% de población española.

Para echar números se inventaron las calculadoras, hace ya muchos años. Y de de la evolución las calculadoras nació el microprocesador, hace ya 38 años. Tiene huevos, por otro lado, que como respuesta a la crisis la vicepresidenta diga que nos vamos a montar en el chip para abandonar el crecimiento basado en el ladrillo. Porque para eso han tenido tiempo, y de sobra, dos administraciones socialistas, otras dos peperas, unas tres y media socialistas de nuevo y un trozo de la de la UCD. A buenas horas, (vice)presidenta.

Retomemos que me enciendo. Lo que les voy a contar hoy en Cosas que Sí se dan en Informática es cómo cuentan los computadores. O, dicho de otro modo, cómo representan los números los ordenadores.


A estas horas es vox populi (excepto quizá para nuestros representantes públicos) que los computadores utilizan ceros y unos para representar la información. Antiguamente eran campos magnéticos en núcleos de ferrita, hoy pueden ser en forma de surcos en discos ópticos y mañana quizá sea en cuantos de energía en átomos. Dejando la lógica difusa al margen la cosa sigue teniendo la misma base: haber o no haber. 1 o 0. Y ahora veamos qué es eso del binario.

Utilizar dos caracteres para representar números tiene la misma dificultad que emplear 10. Que es lo que hacemos los humanos continuamente. El llamado sistema decimal no es más que un sistema posicional de representación con diez símbolos (0, 1, 2, 3, ..., 9). Es posicional porque la posición importa en el valor de un símbolo, como sabrán todos aquellos que han sido llamados "ceros a la izquierda". Así, el número 459 significa, exactamente:

4*102 + 5*101 + 9(*100)

El carácter más significativo -el 4- lo colocamos más a la izquierda y se multiplica por la base -10- elevado a su posición -la tercera-. Análogamente para el resto.

Con el binario, la representación es idéntica. Sólo tenemos que tomar la base 2.

1*28 + 1*27 + 1*26 + 0*25 + 0*24 + 1*23 + 0*22 + 1*21 + 1*20

Nos da exactamente 459, si le apetece hacer la suma. O nos da exactamente 111001011. Utilizar un sistema decimal en lugar de cualquier otro es completamente arbitrario o fortuito. De hecho, no está del todo claro por qué gastamos un sistema decimal, aunque muy posiblemente sea por tener diez dedos. Desde luego, es un argumento de peso.

Como escribir 111001011 es un rollo, incluso para una cantidad pequeña como 459, los informáticos nos apañamos para representar grandes números empleando otras bases más cómodas, como la octal o hexadecimal. En la primera empleamos los símbolos 0, 1, 2, 3, 4, 5, 6 y 7. En la segunda, todos los anteriores y además 8, 9, A, B, C, D, E y F. Es inmediato pasar de binario a ocal y hexadecimal (y a la inversa). Tan sólo tenemos que agrupar los símbolos binarios de 3 en tres y representar el correspondiente caracter octal, o de cuatro en cuatro y sustituir por el correspondiente símbolo hexadecimal:

111 001 011 = 713 (7*82 + 1*81 + 3*80)
1 1100 1011 = 1CB (1*162 + 12*161 + 11*160)


¡Maravilloso!. Ya tenemos hasta cuatro maneras de representar lo mismo. Cualquier número que se nos ocurra puede ser representado de cualquiera de ellas, aunque no dentro de un computador. porque la aritmética de un computador es finita, lo que no es sino un modo de decir que tiene límites: números mayores de cierto rango (o menores) no nos cabrán. Pero no nos preocuparemos por ellos todavía.

¿Y nos sirve de algo lo visto en tiempos de crisis?. Pues no, porque aún no podemos representar la realidad, mucho más dramática que lo visto hasta ahora. La cruda realidad cuenta con números rojos.

Los números rojos son negativos y usted lo tiene muy fácil para representarlos porque hace trampa: pone un - delante de ellos y ya. ¡Es hacer trampa porque añade un símbolo que no estaba en el conjunto inicial de diez!. ¿Cómo se las apañaría para representar números negativos sin ningún símbolo añadido?. A esa pregunta respondieron los informáticos hace mucho tiempo.

La solución fácil: reservamos una posición, la más significativa, para indicar si un número es positivo o negativo. El resto del número nos da su magnitud. A ésta forma de representación se le llama signo y magnitud, por razones que explicaré otro día. Si el primer símbolo es un cero, el número es positivo. Si es un 1, negativo.

0 000 -> +0   |   1 000 -> -0
0 001 -> +1   |   1 001 -> -1
0 010 -> +2   |   1 010 -> -2
...

Ésta forma de representación, además de contar con la curiosidad de disponer de dos ceros, es un peñazo, porque requiere analizar el signo antes de ponerse a operar con los números y hacer sumas y restas y aritmética en general. Hay métodos más cómodos.

La forma que efectivamente se gasta un montón es el llamado Complemento a 2. El Ca2 representa los números positivos como los naturales que vimos al principio. Los negativos, en cambio, los representa invirtiendo todos lo bits y sumando uno al resultado (lo que se llama hacer el complemento a dos1). Sumar, por cierto, es tan fácil como en base decimal: cero mas cero es cero; cero mas uno, uno; y uno más uno, cero y me llevo uno.
Veamos un ejemplo de representación en Ca2:

0000 -> +0   |   1111 -> -1 ( invertimos1(0001) = 1110 + 1 = 1111 )
0001 -> +1   |   1110 -> -2 ( invertimos2(0010) = 1101 + 1 = 1110 )
0010 -> +2   |   1101 -> -3 ( invertimos3(0011) = 1100 + 1 = 1101 )

¿Por qué es tan interesante esto que a primera vista parece un lío?. Bueno, podemos ver a la primera si un número es positivo o negativo. Y ya no tenemos dos ceros. Y sobretodo nos permite sumar a la primera cantidades negativas y positivas. Un ejemplo:

-20 + 10 = -10

-20 -> invertimos20(010100) = 101011 + 1 = 101100
+10 ->                                     001010
                                           110110 -> Ca2(110110) = 001001 + 1 => 001010 = 8 + 2 = 10

La suma nos ha dado -10 directamente, sin tener que hacer ajustes posteriores. Y no los habrá que hacer, a menos que que el resultado no sea representable (no nos quepa) en la longitud de bits máxima que tenemos. Así que nosotros nos quedaremos con éste formato para representar los enteros.

De todos modos, este formato tampoco representa la realidad. Porque si usted va al banco, transfiere unos 120 euros y le cobran un 4% de comisión (5 euros) se quedará de piedra. Primero: porque hay que ser joputas para cobrar un 4% de comisión por una mierda de operación bancaria que se realiza a través de Internet. Y segundo, porque el 4% de 120 no son 5 euros, sino 4.80. ¿Quién se ha quedado sus 20 céntimos en tiempos de crisis?

El banco, evidentemente. Porque sus ordenadores no pueden representar cantidades decimales, no al menos hasta que yo les explique cómo. Y redondean (que no truncan) para fastidiarle a usted. Pero les contaré cómo representar cantidades fraccionales sin hacer de nuevo trampa e introducir más símbolos como el '.'.

¡Eso será más adelante, en Cosas que Sí de dan en Informática!

1 ver la referencia para una explicación más convincente.

Apuntes de clase. ¡Esto se estudia en primero!
Sistema binario. Wikipedia.
Complemento a dos. Wikipedia.

Posts relacionados

10 enero, 2009

Cómo escribir código inmantenible

Últimamente algo ando espeso, lo siento. Tengo la cabeza tan llena de cosas del trabajo que no me da tiempo a escribir nada bueno para el blog, ni a pensar en nuevos temas ni nada. Disculpen si esto baja un poco el ritmo de actualización. Y de originalidad.

Como saben ustedes (y si no lo saben se lo cuento), yo soy programador. Programador de C, para ser más exacto. Ahórrense las condolencias: me gusta mi trabajo. Programo sistemas empotrados, que es como ordenadores, pero más pequeños y desnudos. Todo una delicia, si no atendemos al componente sexual del asunto.

La cuestión es que hace unos meses encontré este vetusto texto de Internet: How to write unmanteinable code, y me pegué una jartá a reír. Comento, para quien no lo sepa, que el código con el que funcionan los programas difícilmente es escrito por una sola persona y, en cualquier caso, es muy fácil que otra tenga que leerlo y entenderlo para actualizarlo, añadir funcionalidades y quitar otras. A eso se le llama mantener una aplicación. El problema que tenemos es que, por lo general, cada uno tiene un estilo a la hora de programar y entender un programa que ha escrito otro puede ser tremendamente complicado.

Para conseguir código mantenible existen centenares de guías. Y normas. Pero sólo existe una para escribir código inmantenible. Les traduzco algunas de las recomendaciones más graciosas que he encontrado en ella1.

[Advertencia]: si usted no es informático, el concepto que tenemos usted y yo de lo que es gracioso y lo que no lo es puede variar mucho.


Nombrado de variables

Si llamas a tus variables a, b, c, entonces será imposible encontrarlas usando un buscador común. Además, nadie tendrá ni idea de para qué sirven. Anima a los lectores de tu programa a encontrar cada ocurrencia leyendo todas la lineas de todos los ficheros: así entenderán mejor el programa.

A.C.R.O.N.Y.M.S.: utiliza acrónimos para mantener el código carente de emoción. Los hombres de verdad jamás explican los acrónimos: los entienden por genética.

Reusa nombres: nunca está de más ser ecológicos. Siempre que el lenguaje lo permita, llama a funciones, variables y parámetros con los mismo nombres. El objetivo es obligar al mantenedor a que conozca perfectamente las reglas de ámbito del lenguaje.

Elige nombres con connotaciones emocionales irrelevantes y opera con ellos, p.ej.

marypoppins = (superman + starship) / god;

Esto confundirá al lector, que suele asociar nombres de variable de algún modo con el valor que contiene.

Asegúrate de que cada función realiza un poco más (o menos) de lo que su nombre indica. Como simple ejemplo, un método llamado isValid(x) puede tener como efecto colateral convertir x a binario y almacenarlo en una base de datos.

Emplea referencias ocultas a películas. Usa nombres constantes como LancelotsFavouriteColour en vez de azul y asígnalo al valor hexadecimal de 0x0204FB. El color es idéntico al azul de la pantalla y el programador mantenedor tendrá que buscar alguna herramienta gráfica para asaber qué color es. Sólo alguien muy familiar con los Monty Python (y los caballeros de la mesa cuadrada) sabrá que el color favorito de Lancelot era el azul. Si el programador mantenedor no puede citar de memoria las películas enteras de los Monty Python, entonces no tiene futuro como programador.

Camuflaje

Incluye secciones de código que están comentadas pero que a primera vista no lo estén. Por ejemplo:
for(j=0; j < array_len; j+ =8)
{
  total += array[j+0 ];
  total += array[j+1 ];
  total += array[j+2 ]; /* Main body of
  total += array[j+3]; * loop is unrolled
  total += array[j+4]; * for greater speed.
  total += array[j+5]; */
  total += array[j+6 ];
  total += array[j+7 ];
}

Recuerda al mantenedor las reglas de asociación añadiendo instrucciones que resultan ser comentarios.
Importantvalue = a/*p /* divide by the value pointed */;

Si tienes suerte y el compilador permite comentarios anidados, puedes reservar muchas sorpresas hasta que se descubra que no existe división alguna.

Esconde las definiciones de las macros, entre comentarios inútiles. Así el programador se aburrirá y no llegará a descubrirla. Asegúrate de que la macro reemplaza una asignación perfectamente normal por cualquier operación rara, por ejemplo:
#define a=b a=0-b

Aunque ya tengas una variable llamada xy_z, no hay ninguna razón para que no puedas emplear también otras con nombres similares, como xy_Z, xy__z, _xy_z, _xyz, XY_Z, xY_z, and Xy_z. Sé creativo: variables que se distinguen tan sólo por las mayúsculas y barras bajas tienen la ventaja de confundir a quienes recuerdan los nombres por cómo suenan, en vez de por su representación exacta.

Elige el mejor operador de sobrecarga: En C++, sobrecarga los operadores +,-,*,/ para hacer cosas totalmente diferentes de suma, resta... Después de todo, si Stroustroup puede usar el operador de desplazamiento para entrada/salida, ¿por qué no ser igual de creativo?. Si sobrecargas +, asegúrate de que i = i + 5 tiene un significado completamente distinto a i += 5.

Documentación

Miente en los comentarios. Es muy sencillo: no actualices los comentarios al actualizar el código.

Documenta cómo y no el porqué. Documenta sólo los detalles de lo que el programa hace (incluyendo los autoincrementos), no lo que pretende conseguir. Así, si hay algún error, el que lo tenga que arreglar no tendrá pistas para sabe qué es lo que el código debería estar haciendo.

Nunca documentes errores en el código. Si sospechas que hay un bug, mantenlo en secreto. Si crees que el código puede ser mejorado o reorganizado, por el amor de Dios, no lo digas. Recuerda las palabras en la película Bambi: Si no puede decir nada bonito, mejor no digas nada. Qué sucederá si el que lo escribió ve tu comentario?. ¿Y un cliente?. ¡Podrías estar despedido!. En cambio, un comentario del tipo: /* This needs to be fixed! */ puede hacer maravillas, sobretodo si no se sabe a qué se refiere. De este modo nadie podrá ser criticado.

Diseño

Gasta arrays de tres dimensiones: montones de ellos. Mueve datos entre ellos de originales maneras, llenando las columnas de uno con las filas de otro. Si el desplazamiento es de uno, sin razón aparente, bien hecho. Hace que el mantenedor se ponga nervioso.

Utiliza todas la variables globales que puedas. Si Dios no hubiera querido que usáramos variables globales, no las habría inventado. Cada función debe usar y dar valor a un par de variables globales al menos, incluso aunque no exista ninguna buena razón para ello. Al fin y al cabo, el mantenedor se figurará que es una especie de detective y estará contento de cumplir una tarea que separa a los verdaderos programadores de los aficionadillos.

Testing

Que tu proyecto sólo funcione en modo debug. Si tienes distintas configuraciones de proyecto, asegúrate de que tan sólo funciona con una de ellas. Es muy sencillo de realizar con directivas de preproceso: #define TESTING te da la oportunidad de tener dos secciones del código independientes. Dentro de #ifdef TESTING asigna los valores necesarios para que funcione el programa. Si alguien elimina el define, o mejor aún, no compila con la directiva adecuada, el programa no funcionará.

Testear es de cobardes. Un programador valiente se saltará este paso. Demasiados programadores temen perder su empleo y tienen miedo del jefe y del cliente. Este miedo les paraliza y reduce su productividad. Estudios han demostrado que eliminando las fases de test se puede adelantar las fechas de entrega, algo positivo para el proceso. Sin el miedo, las innovación y experimentación pueden florecer. El trabajo del programador es producir código, y mantenerlo es tarea de otros.

(...)

En fin, la lista tiene años y tiene multitud de ideas gloriosas para escribir código inmantenible. Si tiene tiempo y es usted programador de Java, C o C++ échele un vistazo. Si quiere escribir bien, en cambio, lo tiene más complicado.

1 La traducción es muy libre, aviso. Yo he escogido ejemplos que se aplican a C porque es el lenguaje con el que trabajo. Y se pueden hacer muchas muchas guarradas en C...

How to write unmmaintainable code
C traps and Pitfalls, de Andrew Koenig pone el ejemplo del puntero-comentario.

Posts relacionados

22 noviembre, 2008

CSI XIV: Lenguajes y gramáticas (i II)

Es obligatorio leer el post anterior...

Nos habíamos quedado en una pregunta: ¿Cómo se relaciona las gramáticas formales con los lenguajes de programación de alto nivel? Pues verán, los lenguajes de programación están especificados mediante una gramática que determina la secuencia de palabras aceptable en un programa. Y nosotros, los humanos, hacemos algo parecido cuando queremos entendernos. Si yo les digo "rosa un pasea perro tranquilamente" no estoy hablando español, por más que las palabras que gaste existan en la lengua española. Existe una gramática que debo cumplir.

Es importante notar una diferencia respecto al ejemplo anterior de las aes y las bes. Lo que antes eran letras que conformaban el alfabeto ahora se corresponden con palabras que integran el vocabulario. Las reglas de la gramática castellana se aplican a las palabras, las cuales reciben categorías gramaticales, como sustantivo, adjetivo, verbo, etc. En informática en vez de palabras hablamos de tokens. Tokens típicos son identificador, declaración, expresión...


Vamos a crear una sencilla gramática, que será una burda imitación de nuestro lenguaje. Los símbolos no terminales serán: ORACIÓN, SUJETO, PREDICADO, DETERMINANTE, SUSTANTIVO, VERBO, ADJETIVO y ADVERBIO. Los no terminales serán el conjunto {un, el, este, aquel, perro, gato, pastel, azul, pequeño, pasea, camina, garbosamente, rápidamente}.

En primer lugar estableceremos la relación entre los terminales y las categorías gramaticales representadas por símbolos no terminales. En nuestro caso esto es evidente y la representación se entenderá fácilmente:

DETERMINANTE = {"un", "el", "este"}
SUSTANTIVO = {"perro", "gato", "pastel"}
VERBO = {"pasea", "camina"}
ADJETIVO = {"azul", "pequeño"}
ADVERBIO = {"garbosamente", "rápidamente"}


Si la secuencia de letras corresponde a una palabra con un no-terminal asociado viene determinado por el análisis léxico. Garvosamente no es un adverbio, por mucho que lo parezca: es una burrada. Decimos entonces que se trata de un error léxico.

Nuestra gramática relaciona los símbolos no terminales y tiene esta pinta, que seguro les sonará del cole:
ORACIÓN := SUJETO PREDICADO
SUJETO := DETERMINANTE SUSTANTIVO | DETERMINANTE SUSTANTIVO ADJETIVO
PREDICADO := VERBO | VERBO ADVERBIO
1

Tomando el no-terminal ORACIÓN como símbolo inicial, ya pueden dedicarse a averiguar si los siguientes secuencias de palabras caen dentro de nuestro pequeño lenguaje. Por ejemplo, son parte de nuestro lenguaje: este perro azul camina rápidamente, un gato pasea o Este pastel azul camina garbosamente. En el último caso la oración no tiene ningún sentido, pero es sintácticamente impecable. El último paso para dotar de sentido una oración en un lenguaje es realizar un análisis semántico. En las lenguas naturales éste depende del contexto y es posible que en cierto ámbito pudiera tener sentido que un pastel caminara y, además, lo hiciera con cierto estilo.

Nuestra especificación de un lenguaje deja mucho que desear, por supuesto y sólo recuerda vagamente a los idiomas que hablamos. Los lenguajes que usamos los humanos son lenguajes naturales y la mayoría de las gramáticas que emplean son ambiguas, esto es, para una secuencia de entrada admiten distintas interpretaciones. Los lenguajes informáticos tratan de eliminar toda posible ambigüedad mediante reglas expresas en el tratamiento de la entrada, reglas de precedencia... sin embargo hay ocasiones en los que el comportamiento del compilador, que es quien debe leerse el programa y determinar su validez, se deja sin determinar.

Y en este punto ha aparecido un concepto nuevo: el compilador. El compilador es un programa que, tomando un conjunto de archivos con un lenguaje que podemos entender, genera un archivo ejecutable: una imagen en memoria que se puede ejecutar. Un programa, vamos. Y para entender lo que pone en el fichero, el compilador tiene que realizar las mismas acciones que acabamos de describir.

Es decir, que el compilador primero lee letra a letra el archivo y crea una secuencia de tokens. En nuestra analogía, si el compilador tomara como entrada una frase como Un pastel pasea azul, la salida del análisis léxico sería la secuencia: DETERMINANTE SUSTANTIVO VERBO ADJETIVO. El compilador a veces duda: si lee una p tras un espacio no sabe si corresponde a un SUSTANTIVO (pastel, perro) a un VERBO (pasea) o a un ADJETIVO (pequeño). Y a veces el programador piensa que el compilador va a entender una cosa y entiende otra. (Estoy en condiciones de poner ejemplos a petición de los informáticos).

Con la secuencia de tokens formada, el compilador toma el símbolo inicial de la gramática y juega a validar esa secuencia con las reglas de la misma (análisis sintáctico). De este modo decide si un programa es correcto. Que un programa sea sintácticamente correcto no significa que funcione como el programador espera, del mismo modo que nuestro ejemplo del pastel paseante funcionaba sintácticamente sin tener sentido. A veces las frases sin sentido nos hacen gracia, pero un programa sin sentido no hace ni puta gracia.

En serio.

Precisamente leí hace poco que el primer compilador fue desarrollado por una mujer, Grace Hopper, una de las pioneras de la informática. Programó los computadores más importantes de la época (años 50 y 60) y partici´po en el desarrollo de los lenguajes COBOL y FORTRAN, dos de los lenguajes con más arraigo entre los matemáticos y físicos y que son tan buenos que hoy en día, más de 40 años después, se siguen empleando

En resumen, quizá hayan visto ustedes el código de algún programa y les haya parecido complicadísimo (sin contar que hay gente que escribe código fatal). Desengáñense. Ese lenguaje hay un conjunto relativamente reducido de tokens y su gramática es tremendamente más sencilla y restrictiva que todo aquello que les enseñaron en el cole, con sus excepciones y dialectismos.

¡Los programadores estamos, definitivamente, sobrevalorados!

1 Los informáticos me podrán decir que esta gramática no se ajusta a la forma normal de Chomsky y mogollón de cosas más. Es cierto que no he querido complicar demasiado la explicación: no pretendo que nadie apruebe una asignatura de teoría de autómatas o lenguajes formales gracias mis posts. Como dije, pretendo tan sólo mostrar que Informática es algo más que páginas web y bases de datos.

Apuntes de clase.
En la wikipedia podemos ver un diagrama sencillo de cómo actúa un compilador.

Posts relacionados

CSI XIV: Lenguajes y gramáticas (I)

Éste post pretende ser, no exactamente una continuación, sino un complemento a éste post acerca del lenguaje de las máquinas. Recordando un poco de qué iba, vimos cómo en principio las instrucciones recibidas por máquinas y computadores eran de tipo mecánico. Con el tiempo se pudo almacenar estas instrucciones en forma de memorias magnéticas donde series de niveles de tensión determinaban el comportamiento de los componentes del procesador.

Se desarrollaron luego distintos niveles de abstracción sobre el lenguaje en el que se expresaban estas instrucciones (programas). El lenguaje ensamblador consistía en una serie secuencial de códigos que representaban directamente las instrucciones que podía ejecutar un procesador. Era completamente dependiente del mismo. Sin embargo, por encima del ensamblador surgieron lenguajes con un nivel mayor de abstracción, independientes de la máquina en el lenguaje que emplean. Se les llama lenguajes de alto nivel. Los lenguajes más empleados hoy en día son de este tipo, aunque hay muchas diferencias entre ellos.

En el post de hoy generalizaremos la representación de un lenguaje de alto nivel y veremos en qué medida sus conceptos son tomados de la lingüística y se basan en la teoría de grupos.


Empezaremos desde abajo: llamamos alfabeto a un conjunto finito de símbolos (que llamaremos letras). Una palabra es una secuencia de finita de letras de un alfabeto. Hasta ahí todo normal, ¿no?. Venga un ejemplo: Δ es un alfabeto compuesto por las letras a y b. No nos liemos con más. Algunas palabras posibles sobre ese alfabeto son aaaab o baaba. Al conjunto de todas las palabras que se pueden formar con este alfabeto se le denomina el lenguaje universal sobre el alfabeto Δ, siempre que incluyamos en el mismo la palabra vacía λ (lambda). La palabra vacía tiene longitud 0.

Dado que el lenguaje es un conjunto, se pueden realizar operaciones sobre el mismo y, dependiendo de las propiedades que cumpla, le daremos un nombre u otro. Eso se lo dejaremos a los matemáticos.

Los lenguajes universales, pese a ser fascinantes por sus propiedades (digo yo) de poco nos sirven en la práctica. Volviendo a un plano más cercano a la vida real, los seres humanos no sólo empleamos un conjunto finito de palabras, sino que además empleamos un orden determinado entre ellas. A este orden se le denomina sintaxis. Y la gramática determina las reglas en las que se deben combinar las palabras.

En informática (y matemáticas) una gramática tiene una definición formal: consta de un conjunto de terminales, un conjunto de no-terminales (o símbolos auxiliares), un símbolo no-terminal inicial y un conjunto de las reglas de producción. Las reglas de producción determinan la generación de las palabras del lenguaje. Con un ejemplo esto queda clarísimo.

Nuestro alfabeto Δ = {a,b,&lambda} podría ser el conjunto de terminales. S será el símbolo no terminal inicial y A es el otro no terminal. Las reglas de producción podrían ser estas (enseguida las explico):

S := bS | aA | λ
A := aS | bA


Posibles derivaciones:
S -> bS -> bλ: b forma parte del lenguaje.
S -> bS -> baA -> babA -> babaS -> babaλ : baba forma parte del lenguaje.


Ésta gramática genera el lenguaje de las palabras con un número par de aes. ¿No me creen?. Pues vean. El juego consiste en, empezando en el símbolo inicial, quedarse sin no-terminales. Cuando queden sólo terminales se termina y obtenemos una palabra. El símbolo inicial es S y decimos que S deriva directamente bS ó aA ó la palabra vacía (| significa 'o'). Podemos escoger cualquiera de las tres derivaciones. Si escogemos la palabra vacía ya hemos acabado, porque nos hemos quedado sin no-terminales: la palabra vacía tiene 0 aes y forma parte del alfabeto. Si escogemos bS tenemos que quitar la S, y derivamos de nuevo. Posibles secuencias de derivaciones a partir de ésta podría ser las que hemos visto en el ejemplo de arriba o la que se ve en la figura. (en la figura he empleado sigma para referirme al alfabeto, lo cual suele ser una convención).

¿Por qué no prueban a jugar siguiendo las reglas? Verán que todas las palabras tienen 0 o un número par de aes.

Y ahora es el momento de cambiar de post, porque si no, esto queda muy largo. Vayamos al siguiente para ver qué tiene esto que ver con los lenguajes de programación...

1 Esta entrada no se ve bien con IE6, en parte porque yo no he escrito bien HTML (que Firefox se traga), en parte porque Internet Explorer no muestra casi nada bien. Pero bueno, no me enseñaron HTML en la carrera, me enseñaron cosas como ésta que les he contado. Por eso esta sección se llama Cosas que Si se dan en Informática,

Apuntes de clase. Anda que no hace tiempo que estudié esto...

Posts relacionados

09 septiembre, 2008

Crear subetiquetas en Blogger

Que Blogger no es especialmente flexible a la hora de personalizar el blog es bien conocido. Lo sabrá cualquiera que haya intentado juguetear un poco con el template. Wordpress ofrece muchas más características, además de plugins de terceros que se pueden integrar fácilmente (supongo) para personalizar el blog. Yo ya me he peleado unas cuantas veces con mi plantilla cuando he querido hacer algunas cosas un poco especiales: ensanchar el blog, introducir el Seguir leyendo, definir entradillas personalizadas, y más recientemente pasar del tamaño fijo y ajustar la anchura del blog al tamaño de la pantalla.

Una cosa que siempre he deseado hacer era utilizar subetiquetas. Es decir: refinar un conjunto de posts (los de la etiqueta Cerveza en este caso) con otras etiquetas. Esto es bastante útil para clasificar los post en la barra lateral, al menos en un blog como el mío. Los gestores de blogs no entienden de subetiquetas y todas las etiquetas tienen la misma jerarquía. Así que el tema consiste en conseguir que en la barra lateral aparezcan, alineadas en forma de árbol, las etiquetas y subetiquetas que yo desee. Encontré un método muy cómodo y personalizable, pero que incluía bastante Javascript (además de tener otros inconvenientes). Yo lo quería sin librerías de funciones ni muchas imágenes ni nada de eso. Finalmente se me ocurrieron dos soluciones de aficionado (que funcionan).


La solución con falsas etiquetas

El problema con Blogger es que no soporta las consultas del tipo: muéstrame todos los posts con la etiqueta 'Cerveza' AND 'Historia'. No le da la gana1. Porque Wordpress sí que lo hace y para muestra, http://curiosoperoinutil.com/category/ciencia/fisica/. Podéis ver que el blog Curioso pero Inútil tiene en la barra, bajo la categoría Ciencia muchas subetiquetas, como por ejemplo Física.

Por otro lado Blogger sí que soporta la búsqueda de cadenas de texto. Así que mi primera solución fue de pillo: emplearía falsas etiquetas con palabras clave, ocultas en cada post que quisiera clasificar bajo la etiqueta de 'Cerveza'. Por ejemplo, en cada post que fuera de Historia de la Cerveza gastaría la palabra oculta cervhistoria. Para ocultarla, basta cambiar la propiedad de estilo display.

Es decir, escribo, al final de cada post de Cerveza con la 'subetiqueta' Historia <span style="display:none;>cervhistoria</span>.

Una vez hecho esto podemos consultar todos los posts con la palabra cervhistoria con la consulta http://civada.blogspot.com/search?q=cervhistoria2.

Y ahora sólo tengo que editar el template del blog para cambiar la barra lateral. A los elementos especiales del blog que están en la barra lateral se les llama widgets. Hay un widget específico para las etiquetas. En él se recorren todos los nombres de etiquetas de tu blog que Blogger almacena (alfabéticamente) y se muestran como una lista html. Sólo tenemos que 'buscar' la etiqueta deseada (Cerveza) y entonces iniciar una sublista html. En ella añadiremos los enlaces a las búsquedas de palabras ocultas. (el añadido está en rojo)

Buscamos el widget en el template y hacemos esto (alguien que haya hecho algo de informática lo entenderá enseguida):

<b:widget id='Label1' locked='false' title='Etiquetas' type='Label'>
<b:includable id='main'>
<b:if cond='data:title'>
<H2><data:title/></H2> <!-- Esto es la palabra 'Etiquetas' -->
</b:if>
<DIV class='widget-content'>
 <UL> <!-- Aquí empieza la lista principal -->
  <b:loop values='data:labels' var='label'>
  <LI>
   <b:if cond='data:blog.url == data:label.url'>
   <SPAN expr:dir='data:blog.languageDirection'>
    <data:label.name/>
   </SPAN>
   </b:if>
   <b:else/>
    <A expr:dir='data:blog.languageDirection' expr:href='data:label.url'><data:label.name/></A>
   </b:if>
   <SPAN dir='ltr'>(<data:label.count/>)</SPAN> <!-- Número de posts con la etiqueta -->
   <b:if cond='data:label.name == "Cerveza"'> <!-- Aquí empieza lo bueno -->
    <UL style='margin-top:5px;'>
    <LI><A href='http://nombredelblog.blogspot.com/search?q=cervhistoria'>Historia</A></LI>
    <LI><A href='http://nombredelblog.blogspot.com/search?q=cervestilos'>Estilos</A></LI>
    </UL> <!-- Fin de la sublista -->
   </b:if>

  </LI> <!-- Fin de la entrada de Cerveza -->
  </b:loop> <!-- Fin del bucle -->
 </UL> <!-- Fin de la lista de entradas -->
<b:include name='quickedit'/>
</DIV>
</b:includable>
</b:widget> <!-- Sanseacabó el widget -->

Y funciona. Veamos las ventajas de este método.
  • Es relativamente sencillo.
  • No hay que trastear mucho el template. Aunque por cada nueva 'subetiqueta' virtual tendremos que añadir una linea nueva, claro.
  • Es una idea cojonuda
Pero veamos las desventajas
  • No son etiquetas de verdad. Nadie se podrá suscribir a los posts de Historia de la Cerveza. Lástima por los historiadores maltasianos. Tampoco tenemos cuenta de posts subetiquetados. Lástima por los amantes de las estadísticas
  • 'Etiquetar' muchos posts antiguos con las nuevas palabras ocultas es un rollo.
  • El día que a Blogger le de por añadir esta característica tendremos que etiquetar de nuevo y desmontar la paraeta.
Así que mirando el template di con una solución más bestia y menos elegante, pero con etiquetas de verdad.

La solución a lo bruto

Es muy sencilla: consiste en NO añadir a la lista principal las subetiquetas Y AÑADIRLAS expresamente bajo la etiqueta principal. Para eso gastaremos dos millones de expresiones condicionales. ¿Por qué?. Porque, que yo sepa, Blogger no entiende expresiones condicionales compuestas. Supongamos que tenemos tres subcategorías bajo Cerveza que son Historia, Estilos y Ronda.

La cosa es (atentos, amantes del pseudo-código)
PARA todas_las_etiquetas HACER
SI nombre_de_etiqueta NO_ES Historia NI Estilos NI Ronda ENTONCES
 (abrimos lista)
 ...Código del widget...
 SI nombre_de_etiqueta ES Cerveza ENTONCES
   PARA todas_las_etiquetas HACER
   (empezamos sublista)
   SI nombre_de_etiqueta ES Historia O Estilos O Ronda
     Añadimos enlace a la etiqueta como el resto.
   (cerramos sublista)
 (cerramos lista)
 PUES_YASTA


El único problema es que en Blogger (QUE YO SEPA) no se puede poner ni NI ni O. Lo cual es triste para la programación. Porque hay que añadir un mogollón de código. Algo así:
<div class='widget-content'>
<ul> <!-- Inicio de la lista -->
<b:loop values='data:labels' var='label'>
 <b:if cond='data:label.name != "Estilos" '>
 <b:if cond='data:label.name != "Historia" '>

 <li>
  <b:if cond='data:blog.url == data:label.url'>
   <span expr:dir='data:blog.languageDirection'>
   <data:label.name/>
   </span>
  <b:else/>
   <a expr:dir='data:blog.languageDirection' expr:href='data:label.url'>
   <data:label.name/>
   </a>
  </b:if>
  <span dir='ltr'>(<data:label.count/>)</span>
 </li>
 </b:if></b:if> <!-- Tantos cierres como subetiquetas que no han de estar -->
 <b:if cond='data:label.name == "Cerveza"'>
  <ul style='margin-top:5px;'> <!-- Inicio la sublista -->
  <b:loop values='data:labels' var='label'>
   <b:if cond='data:label.name == "Estilos" '>
    <li>
     <b:if cond='data:blog.url == data:label.url'>
      <span expr:dir='data:blog.languageDirection'>
      <data:label.name/>
      </span>
     <b:else/>
      <a expr:dir='data:blog.languageDirection' expr:href='data:label.url'>
      <data:label.name/>
      </a>
     </b:if>
     <span dir='ltr'>(<data:label.count/>)</span>
    </li>
   <b:else/>
   <b:if cond='data:label.name == "Historia" '>
    <li>
     <b:if cond='data:blog.url == data:label.url'>
      <span expr:dir='data:blog.languageDirection'>
      <data:label.name/>
      </span>
     <b:else/>
      <a expr:dir='data:blog.languageDirection' expr:href='data:label.url'>
      <data:label.name/>
      </a>
     </b:if>
     <span dir='ltr'>(<data:label.count/>)</span>
    </li>
   </b:if></b:if> <!-- Tantos cierres como subetiquetas -->
  </b:loop>
  </ul> <!-- Fin de la sublista -->
 </b:if> <!-- Si era cerveza -->

 </b:loop>
</ul> <!-- Fin de la lista -->


Y no queda nada elegante, pero funciona, y funciona bien, como pueden ver en la barra de al lado. Pasemos pues a la enumeración de pros y contras (esta vez en orden inverso).

Contras de esta solución:
  • Es un peñazo para el template, sobretodo si un desea emplear muchas subetiquetas. Hay que tocar mucho código.
  • Algún día Blogger le dará soporte directamente y todo esto se irá al garete
Ventajas:
  • Es muy fácil añadir las subetiquetas a los posts ya creados.
  • Tenemos etiquetas de verdad, a las que uno se puede suscribir. Los maltasianos estarán contentos. Y los amantes de las estadísticas.
Hay que hacer notar que, aunque pongamos mogollón de código condicional en el template, éste no se transmite y por tanto no hace que la página 'pese' más. Sí que puteamos un poco a los servidores de blogger, que son los que procesan el template junto con los datos del blog y crean el código html que es efectivamente transferido (el tiempo de proceso del servidor suele ser nimio en comparación con el de transmisión). Pero que se jodan. Haber dado soporte directamente, leñe.

Y ya está. ¿A que mola?. Yo no lo había visto en ningún blog de Blogger. Sólo hace falta que lo diga para que me salgan 15 ejemplos. Si alguien conoce una manera mejor de hacer lo mismo, que me lo diga, por favor.

Actualización

ha realizado en su blog una explicación detallada del segundo método. La pueden consultar aquí: Parte I, Parte II y Parte III. Si después de leerlos les queda alguna duda... ¡léanlo otra vez, porque está muy bien explicado!. Bueno, no, pregunten si quieren...

1 Si sí que lo hace y yo no lo he conseguido, dejen de leer, por favor. Y díganmelo, porque me he vuelto loco probando hasta decidir (yo solo) que no se puede.
2 Evidentemente, hay que emplear alguna palabra que no vayamos a usar en posts que no sean de esa subcategoría.


Posts relacionados

28 agosto, 2008

El fallo en BGP, algo nuevo pero no tanto

Han saltado las alertas. La comunidad informática mundial se enfrenta a la mayor amenaza a la privacidad en Internet. Se ha desvelado un fallo de seguridad que pone en peligro las comunicaciones de todo el tráfico en la red de redes. De hecho esta página que usted está leyendo podría ser falsa y estas palabras no haberlas escrito yo. Es incluso más posible que alguien esté tan sólo fisgoneando todas las páginas que usted pide. Y este es el segundo super fallo desvelado en apenas unos meses.

Supongo que eso es lo que pensará usted si ha leído las informaciones (la I y la II) aparecidas en El País acerca del fallo en el protocolo BGP. No seré demasiado duro juzgando a los articulistas y diré simplemente que no lo han explicado o entendido muy bien. O traducido, porque la cosa viene bastante bien explicada en Wired y la información de El País copia sólo la mitad y traduce mal la parte que copia.

Lo que les cuento en el primer párrafo es totalmente cierto. Pero es que es totalmente cierto incluso sin existir el fallo de seguridad descubierto ahora. En este post, yo, que tengo más espacio que la gente de El País (en eso se les disculpa) les cuento de qué va el fallo BGP y qué supone de nuevo.


En una frase tiene mucha razón el segundo artículo de El País y es que Internet no se diseñó para ser segura. La información que usted solicita y que le llega a su ordenador fluye por los cables visible para todos: como si usted envía una carta en un sobre abierto. En Internet sería un poco complicado leer la carta, porque la información se trocea en pequeños paquetes, pero la idea es la misma: sólo tienes que estar en el lugar adecuado para recoger la información y husmear.

Ese tipo de ataques se llaman de hombre en el medio -man in the middle- y lo han hecho todos los estudiantes de informática en alguna práctica cuando, con un sniffer de paquetes, se ponen a mirar qué es lo que pasa por la red local a la que están conectados todos los ordenadores del laboratorio. Evidentemente, el hombre en medio puede leer toda la información entre origen y destino siempre que no vaya cifrada. Así que la amenaza no es nada nueva: el fallo BGP consiste en un buen modo de situarse en el lugar adecuado y recibir tráfico para fisgonearlo o incluso modificarlo.

Ya conté cómo funcionaba Internet, aunque me hicieron falta dos posts y puede que la cosa no quedara muy clara. La idea es que, para llegar de una red de computadores directamente conectados a otra se utilizan dispositivos llamados routers. Entre dos redes pueden existir varios caminos distintos que pasan por distintos routers. Es necesario que un mensaje que va de una red a otra no permanezca dando saltos sin llegar nunca a su destino. Para ello, los routers seleccionan el mejor camino entre dos redes de entre los muchos que pueden existir. ¿Cómo se ponen de acuerdo?. Pues usando un protocolo (un lenguaje común) que se llama BGP.

BGP significa Border Gateway Protocol. Tiene sentido si pensamos en los routers como puertas que están en las fronteras de las redes, decidiendo cuál es el siguiente salto que un mensaje tiene que dar para llegar a su destino. Sin entrar en detalles, los routers deciden a qué siguiente puerta tienen que encaminarlos en base a la información que le proporciona el resto de routers mediante BGP. Pero ¿qué sucede si un router miente?.

Pues eso es lo que se les ha ocurrido a los chicos investigadores. Que no hay manera de detectar esa situación porque cuando se diseñó el protocolo a nadie se le ocurrió que un router podía mentir. La gracia es que de forma muy sencilla puede mentir para situarse como la mejor puerta por donde pasar para ir a... y así espiar el tráfico que va a... Podría también eliminar todo el tráfico que por allí pasara (blackhole), pero entonces se detectaría fácilmente1. Es más peligroso si modifica ligeramente la información, engañando de este modo a los receptores. Wired comenta que posiblemente este método podría ser usado para espionaje corporativo o militar. Aquí el artículo de El País la caga bien y traduce conceivably (posiblemente) por fue concebido y nos sale con que 'el fallo' fue concebido para ser aprovechado por el espionaje militar. Confunde ademas posibilidad (could) con pasado. Toma ya2.

Por supuesto, la solución no pasa por gastar firewalls, como indica el segundo artículo de El País. Ni por tener cuidado por donde se navega, ni tener antivirus ni saber algo del SO3. Posiblemente la solución consistirá en modificar el protocolo BGP de una forma complicada. En realidad, la auténtica solución pasaría por cifrar todas las comunicaciones que realizamos a través Internet. Las técnicas de cifrado en Internet son conocidas, están bien estudiadas y garantizan la confidencialidad y seguridad durante largo tiempo. Con la implicación de los ISPs (proveedores de servicios, como Telefónica) no debería ser muy difícil conseguir esto.

El auténtico problema de la privacidad y seguridad en Internet es que no le interesa a nadie excepto a los usuarios. Y a los que menos interesa es a los Gobiernos, quienes, siempre invocando nuestra propia seguridad, desean tener la oportunidad de fisgonear e interceptar todo tipo de comunicaciones. Y claro, siendo así, difícil es que se implante, ¿no?.

1 De hecho, eso fue lo que pasó cuando el proveedor de servicios de Internet (quienes suelen mantener los routers) de Pakistan decidió bloquear el tráfico a Youtube de ese modo. El problema fue que routers de países adyacentes fueron así confundidos y el tráfico dirigido a Youtube nunca llegaba a su destino: caía en el agujero de Pakistan.
2 ¡ALT1040 no solo se lo cree sino que añade literatura!
3 Todas ellas prácticas muy recomendadas. Yo añado dos: evite Internet Explorer y bloquee los scripts de las páginas (en Firefox se puede hacer con No-Script).

El agujero de seguridad más grande de Internet. Wired
IP hijacking (Wikipedia inglesa).
Kriptopolis lo cuenta con más detalle y muy bien.

Posts relacionados

09 julio, 2008

El problema de los generales bizantinos

Sitúese.

Está usted en el campo de batalla arrasado, y el viento ondea sus banderas blasonadas, que amenazan la ciudad rebelde, por fin sitiada. Sin embargo, las torturas a las que ha sometido a los infieles no han minado el espíritu enemigo y ningún prisionero ha revelado la cantidad de hombres que guarda la ciudad amurallada. Usted tiene un ejército numeroso, sí, pero las lineas de comunicación y aprovisionamiento han sido destruidas: no podrá mantener demasiado el sitio sobre la ciudad. Urge tomar una decisión sobre el curso de la batalla y, como general que es, deberá consultar con el resto de mandos. La opciones son pocas: atacar todos a la vez o retirarse.

Pero hay un gran problema: sospecha usted que el enemigo se ha infiltrado en sus filas hasta tal punto que es posible que algunos de sus compañeros generales se encuentren, secretamente, sirviendo al enemigo. ¡Hay traidores entre los generales!. Usted sospecha de un par de ellos pero sólo si es capaz de ponerse de acuerdo con otros generales leales en su acusación podrá desenmascarar a los traidores. No puede usted pensar mucho, sus hombres son bravos pero la lealtad se resiente con el estómago vacío. Sólo podrá derrotar al enemigo si ataca con todas las fuerzas de los generales leales así que... ¿podrá usted descubrir a los traidores?. ¿Será capaz usted de poner de acuerdo a los generales bizantinos?.


El problema de los generales bizantinos fue formulado de esta forma (con menos literatura) por Leslie Lamport1,2, uno de los grandes de la informática, en 1982. Desde entonces es uno de los clásicos en la teoría de sistemas distribuidos. ¿Y por qué?. Pues veamos.

Piense que en vez de tener usted un conjunto de generales bizantinos tiene usted un conjunto de computadores distribuidos, llevando a cabo una tarea común. Por ejemplo, una transacción bancaria o la monitorización de la ruta de un avión. Eso suena más informático.

Supongamos que uno de los computadores del sistema deja de funcionar. Hace plof y ya está. No responde a los mensajes del resto, ni ofrece resultados: nada. ¿Como podemos saber que ha fallado? Pues muy fácil, como no hace nada ni envía los mensajes que tiene que enviar ni responde a las llamadas, deducimos que ha fallado. Lo más sencillo entonces es reemplazarlo: que otro haga la misma tarea que realizaba. Si un componente 'se muere' de este modo, decimos que ha fallado en modo crash.

Ahora el mismo componente falla, pero en vez de no hacer nada siempre realiza su tarea más tarde de lo que debería. ¿Cómo nos damos cuenta de este fallo?. Pues tan sólo nos es necesario poner en marcha un reloj cuando debería iniciarse. Si, transcurrido el tiempo determinado, el computador o componente no ha ofrecido un resultado, sabemos que está fallando. Y podemos reemplazarlo por otro, como antes. En este caso decimos que el componente falla en modo performance.

Un pasito más. Supongamos que un elemento ofrece resultados dentro del tiempo establecido pero... estos resultados son siempre incorrectos. Aquí se complica la cosa. ¿Cómo sabemos si falla?. Podemos, por ejemplo, poner dos procesadores a hacer la misma tarea y comparar los resultados pero aún así si estos no concuerdan, ¿cómo saber cuáles son los buenos y cuales no?. En este caso necesitamos tres componentes para tolerar el fallo de uno. Y, en general, 2n+1 para tolerar n fallos. Estos fallos se llaman response. Y los sistemas con tres componentes y un comparador se llaman TMR (triple modular redundancy).

¿Se han fijado cómo cada vez necesitamos más cosas para detectar el fallo?

Y ahora vamos a lo bueno. Supongamos que un componente tiene un comportamiento errático y que, a veces ofrece una respuesta correcta y a veces no. Supongamos que, en las votaciones para detectar la respuesta correcta, este computador acusa a otros de ser los erróneos: a uno le dice una cosa, a otro otra distinta. Actúa como un traidor. Como un traidor bizantino.

Está usted de nuevo en el campo de batalla. Y ya tiene su decisión, atacar o retirarse, que debe transmitir al resto de generales. Los generales leales no se equivocarán, y si creen que atacar es la mejor opción, arrasarán al enemigo. Sin embargo puede que los traidores consigan que decidamos retirarnos. Por otro lado, aunque una mayoría de generales leales haya decidido no atacar, es posible que con el voto de los traidores se fuerce el asalto, cayendo en una trampa mortal.

Usted razona: si sólo hubiera tres generales y uno fuera el traidor... ¿podríamos ponernos de acuerdo la mayoría? Supongamos que yo -Apocapes- envío a ambos la orden de atacar. Los generales Belisarius y Comentiolus reciben mi orden, bien. Y sin embargo, el traidor Belisarius informa a Comentiolus de que mi orden ha sido la de retirada. ¿Que pensará Comentiolus? Pues que, o bien Belisarius es el traidor, como de hecho sucede... ¡o bien que el traidor soy yo, el insigne general Apocapes!. Las dos situaciones serían indistinguibles a los ojos de Comentiolus.

Así que, a pesar de ser mayoría los generales leales (de 2 a 1), no podremos llegar nunca a un acuerdo, pues no sabremos de quien fiarnos. Si dispusiéramos de cartas firmadas... podría enviar cada una de ellas al resto de generales y luego estos intercambiar las mismas. Sin embargo cada general sólo puede controlar lo que transmite, no lo que intercambia el resto.

Entonces ¿y si somos cuatro y uno infiel?. Entonces sí que será posible, puesto que Droctulf, el cuarto general, podrá confirmar que yo di la orden de atacar. ¿Será posible que con más del triple de generales leales podamos acordar una solución?

Por supuesto, la suposición a usted no le vale. Necesita la prueba matemática de cuántos generales son necesarios para llegar a un acuerdo con t generales traidores infiltrados. Pero eso aquí no aparecerá: digamos que simplemente imagina grupos de 3t o menos generales generales (albaneses) que alcanzan el acuerdo: consigue reducir el problema al de los tres generales encontrando por tanto una contradicción. Se requieren 3t + 1 generales leales para acordar una solución.

Las banderas se hinchen con renovada fuerza en el campamento. Los generales que rodean la ciudad son 8 y, si el Imperio Bizantino aún conserva parte de su gloria, los traidores serán dos de ellos. Ahora sabe usted que sin duda podrán alcanzar la mejor decisión.


¿Y qué tiene que ver esto con la informática?. Pues mucho. Si usted no es capaz de evitar los fallos bizantinos en sus programas o componentes que actúan de manera distribuida, sepa que cualquier sistema de decisión requerirá de al menos MAS DEL TRIPLE de componentes que actúen correctamente que los que no3.

Y eso es vital en Tolerancia a Fallos: los sistemas están diseñados para que cualquier tipo de fallo pueda convertirse en uno tipo crash. Aunque se necesite aumentar la circuitería o software de detección, los fallos crash (u omission) -semántica débil de fallo- son por lo general más fáciles de tratar.

Y por eso se le suele dar muerte a los traidores. Es la solución mas ingenieril.

1 El problema de los generales es más antiguo, pero la generalización de Lamport es la más conocida.
2 ¿Sabían que Leslie Lamport también creó LaTeX (Lamport TeX)?
3 Siempre que las comunicaciones sean como las descritas, esto es, que un miembro no puede garantizar que e estado que de él transmiten los otros es cierto.

LESLIE LAMPORT, ROBERT SHOSTAK, and MARSHALL PEASE. The Byzantine Generals Problem. ACM Transactions on Programming Languages and Systems, Vol. 4, No. 3, July 1982, pp 382-401. (enlace)
Lista de Generales Bizantinos

Posts relacionados

30 mayo, 2008

CSI XIII: El lenguaje de las máquinas

Muchas personas han oído hablar alguna vez de lenguajes de programación. Se intuye que los lenguajes de programación sirven para decir a un computador qué instrucciones debe ejecutar para llevar a cabo determinada tarea. Claro, que eso es bastante complicado de realizar. Si nosotros pudiésemos hablar al ordenador le diríamos: "conéctate a Internet" o "Inserta al nuevo cliente en la base de datos". Bueno, eso podemos, podemos hablar al computador todo lo que queramos. El problema es que dudo mucho que entienda nuestro lenguaje.

¿Qué lenguaje entiende un computador?. Según el párrafo anterior la respuesta sería: lenguajes de programación. Otro, quizá, más versado, diga: "el de unos y ceros". En realidad es una pregunta trampa, porque el concepto de lenguaje es una abstracción humana. Si llamamos lenguaje al conjunto de señales que emitimos y recibimos para comunicar algo, entonces el lenguaje de las máquina sería el de las señales eléctricas de tensión que según sus niveles, llamamos 0 lógico o 1 lógico. Si en cambio llamamos lenguaje a una construcción de símbolos (o palabras) gobernadas por una gramática, entonces la respuesta está más cerca de la primera.

No sólo los computadores como su PC entienden lenguajes: muchas máquinas, robots de procesos industriales o ascensores entienden algún tipo de lenguaje que los hace funcionar: qué características tiene ese lenguaje es lo que voy a intentar explicar en los siguientes posts.


[Antes de continuar aviso que me estoy metiendo en camisa de once varas, porque éste no es, ni mucho menos, mi campo natural. Avisado queda el lector avanzado]

Nos ayudará a entender qué lenguajes entienden las máquinas una pequeña revisión histórica. La programación más antigua de una máquina conocida es la de los telares de de Jacqard (1801), donde una tarjeta perforada controlaba el movimiento de las agujas. Aquí, por supuesto, no había computación alguna sino que los agujeros en las tarjetas permitían ciertos movimientos. Otras máquinas utilizaron este sistema y cuando nacieron los primeros computadores, basados en dispositivos electromecánicos, la tarjeta perforada se empleó para asignarles instrucciones y entrada de datos.

Durante la IIGM, por ejemplo, los grandes computadores se utilizaban para los complicados cálculos de trayectorias. Richard Feynman, en los laboratorios de Los Álamos estaba encargado de la sección informática. En el libro ¿Está usted de broma, sr. Feynman? narra cómo los ingenieros debían escribir los programas en tarjetas y, secuencialmente, introducirlas en la máquina para que ésta realizara su trabajo. Si te equivocas en el orden las has fastidiado1. Aquí hay que entender que un computador es un procesador de instrucciones, nada más. Y las instrucciones que puede ejecutar son específicas de cada chip o circuito impreso.

Las tarjetas representaban en ocasiones códigos binarios (ceros y unos). Cada código identifica una instrucción o operación que puede hacer la máquina. Las operaciones que puede hacer un procesador son terriblemente sencillas: lee una instrucción de la tarjeta, multiplica un dato por otro, escribe sobre una posición de memoria... muy poco más, no se crean. Al lenguaje de unos y ceros se le llama código máquina.

Se pueden imaginar lo complicado que era programar de este modo un computador. Pero pronto, en la década de 1950 aparecieron una serie de códigos mnemónicos para identificar cada instrucción. Estos códigos consistían en unas pocas letras, que representaban el nombre de la instrucción seguidas de los operandos propios de cada una de ellas. Por ejemplo:

ADD A, #03h

significa, una vez traducida al código máquina:

00100100 00000011

Y representa la instrucción que dice que hay que sumar un número (3) al registro acumulador, que guarda los resultados de la unidad sumadora. Como ven, se trata de un avance significativo, porque programar se convierte en algo mucho más sencillo. Es muy fácil recordar los códigos de un procesador y más difícil equivocarnos. Por ejemplo, si escribo AFD A, #03h, el programa que traduce estos códigos a lenguaje máquina se quejará, porque no existe ningún código llamado AFD.

El programa que traduce estos códigos al lenguaje máquina se llama ensamblador1. Y también se llama así al lenguaje generado por estos códigos. El paso de lenguaje máquina a lenguaje ensamblador produjo la llamada segunda generación de lenguajes.

Pero el ensamblador tiene algunas pegas: la operación que hemos visto, por ejemplo, pertenece al juego de instrucciones del procesador 8051 y, por tanto, no sirve para ningún otro procesador que no sea compatible con éste. Esto significa que un mismo programa no nos sirve para distintas máquinas y que cada vez que una compañía de chips cambie el juego de instrucciones del procesador, necesitaremos reescribir todos los programas que tenemos (por eso se intenta cambiar pocas veces el juego de instrucciones y cuando se hace es para ampliar, pocas veces para quitar).

Efectivamente, el lenguaje ensamblador está demasiado pegado a la máquina: para programar bien tienes que conocer detalles de la arquitectura del procesador. Por ejemplo, si no sabes cómo funciona ni donde está el registro acumulador del procesador, difícilmente sabrás cómo hacer una suma, por más que exista una instrucción llamada 'ADD'.

Era necesario "independizar" de alguna manera el código que escribíamos en cierto lenguaje del modo en que el procesador actúa. Y por cierto, estamos hablando de 'lenguaje', pero está claro que una secuencia de estos códigos se parece a todo menos a un lenguaje tal y como lo hablamos o leemos.

Sin embargo, imaginen poder decir a cualquier procesador algo así como:

si nuevo_nombre es_igual_a Ramon
entonces dime "Hola Ramon"
y_si_no dime "No te conozco"


Pues bien hay maneras de crear lenguajes de esta manera: a la nueva generación de lenguajes que pudo hacer eso: abstraerse de la máquina donde son ejecutados se les llamó lenguajes de tercera generación y los primeros surgieron hacia los años 60. Los lenguajes de tercera generación se separan tanto del computador que están más cerca del ser humano que de la máquina: por su nivel de abstracción se llaman lenguajes de alto nivel.

Y en el siguiente capítulo veremos cómo se puede conseguir hacer eso.

1 Como le pasó al pobre Apu, el creador del primer programa para jugar al tres en raya. Bart desordenó sus tarjetas perforadas.
2 Quizá se pregunten cómo se ha escrito el propio programa ensamblador. Pues los primeros de ellos, claro está, se escribieron en lenguaje máquina.

Por si les interesa: aquí el juego de instrucciones del 8051

Posts relacionados