Esta mañana ha sonado mi despertador a las 6:00 y en la radio sonaba Sonny y Cher, con su canción I got You Babe. Me he metido en la ducha y el agua me ha salido completamente helada. He bajado de la habitación de invitados en la que estaba alojado y… espera, espera. Esto no me ha pasado a mí. Esto le pasaba a Bill Murray en Atrapado en el tiempo.

Teclarios, hoy, 2 de febrero de 2009, la marmota Phil ha “hablado” y, desgraciadamente, tendremos otras seis semanas de invierno.

Desde 1887, el día de la marmota más famoso se celebra en la impronunciable ciudad de Punxsutawney, en el estado de Pensilvania, donde la marmota Phil (que lleva viva durante más 120 años gracias al elixir de la vida que se toma cada verano durante el Picnic de la Marmota) sale de su guarida y busca su sombra. La fiesta es especialmente celebrada en Estados Unidos pero la llevaron los inmigrantes alemanes que llegaron a esta zona. En Alemania, los granjeros observaban al tejón durante estas fechas del año cuando salía de su madriguera. Si el día estaba soleado, el tejón, asustado por su sombra, volvía a su guarida para hibernar durante seis semanas más. En cambio, en un día nublado, salía sin miedo, augurio de que ya llegaría la primavera. A falta de tejones en Pensilvania, los granjeros alemanes decidieron observar a la marmota para conservar esta tradición.

Pero, como no, algún investigador inquieto decidió poner cifras a la superstición, tirando abajo la efectividad de Phil. Según National Geographic, las predicciones de Phil sólo fueron acertadas en un 39% de las ocasiones. Pero no dejemos que nos amarguen el día. De eso ya se ha encargado Phil prediciendo otras seis semanas de este laaaaaargo invierno que estamos teniendo.

Podréis acusarme de haber tardado demasiado en escribir este post. Cuando lo publique ya será día 3. Pero para mí el día 2 no terminará hasta que me acueste. Y quién sabe qué día será mañana. Tal vez día 2, de nuevo y vuelva a escuchar I got you, babe

Yippikayei, Teclarios!

Esta vez en vez de contar una vivencia, en vez de hacer una reseña de un libro, en vez de, en definitiva, ser yo el participante activo de esta comunicación que tenemos desde hace ya meses, te toca a tí, lector, hablar.

Porque esta vez lo que hago es lanzarte dos preguntas; la razón de por qué te hago estas dos preguntas te la daré en un futuro cercano. Ah, y no temas, son fáciles. Bastante fáciles. Y por si aún te queda algo de miedo o intriga en el cuerpo sobre si sabrás dar con la respuesta correcta, te lo voy a poner aún más fácil. Te diré que, a la vez que te doy la razón por la que lanzo hoy estas preguntas, te ayudaré a asumir la “verguenza interior” que te puede haber causado el hecho de no haber sabido responder a alguna de las dos… aunque ya digo que son bastante asequibles.

Empezamos.

Pensemos en un juego de apuestas sencillo. Tan sencillo como que un grupo N de personas (el caso fácil es N = 2) juegan y apuestan a ver quién es el primero en conseguir que al tirar un dado de N caras (en el caso fácil, vale con una moneda), consigue que su número (cara para un jugador, cruz para el otro) salga, digamos 4 veces. El juego comienza con los N jugadores poniendo la misma cantidad de dinero en un cuenco en el centro de la mesa, que se llevará el jugador que primero alcance esa cifra de apariciones de su número.

Mis dos preguntas están relacionadas con un escenario singular: imaginemos que una vez empezado el juego, por alguna razón hay que parar de jugar. En ese caso, lo que debe hacerse es repartirse el dinero del cuenco en base a la situación actual del juego, es decir, teniendo en cuenta el número de veces que a cada jugador le ha salido su número.

Mis dos preguntas son:

  1. Si son dos los jugadores y en el momento de tener que parar al primero de ellos le ha salido su número dos veces, y al segundo una, ¿cómo deben repartirse el dinero apostado?.
  2. ¿Y si son tres jugadores, a uno de ellos su número le ha salido tres veces y al resto dos?

¡A calcular!

Querido Ndugu,

El otro día un italiano me preguntaba por qué los españoles traducimos todo. ¿Por qué “El equipo A” en vez de “A-team”? ¿Por qué “Parque jurásico” en vez de “Jurassic Park“? ¿Por qué “El coche fantástico” en vez de “Knight Rider“? ¿Por qué “El hombre araña” en vez de “Spiderman”? Y así la lista sigue interminable…

(Otro término por el que podíamos preguntar en esto de las traducciones es “ordenador” en vez de “computadora”, pero es bastante más conflictivo. En realidad también es una traducción, pero esta vez del francés. Y como muchos puristas opinan, hicimos mal en adoptar la palabrita del francés, porque estas máquinas no ordenan, sino que computan (del latin, computāre).)

Mi respuesta fue simple y concisa: porque nosotros respetamos nuestro idioma. Me salió de dentro.

En realidad, esto está empezando a cambiar un poco. Ya podemos ver en nuestros cines y videoclubs películas como “Minority Report“, “Saw” o “Daredevil”. Pero no voy a discutir sobre esto, defendiendo ninguna de las dos posturas, pues se me antoja una discusión interminable, como si emacs es mejor que vi o no (vamos, que todos sabemos que es claramente superior ;) ).

Lo que quería comentar hoy es justo la perspectiva contraria: los angloparlantes nunca traducen nada. Eso tiene una implicación inmediata en las películas extranjeras (o no): siempre las ven en versión original.

Pero como la cabra tira siempre para el monte, vamos a ir hacia nuestro terreno. Como no traducen nada, los propios lenguajes de programación “hablan inglés”.

El caso más claro es el antiguo COBOL. Como fue uno de los primeros lenguajes de alto nivel, ni cortos ni perezosos, casi le hablaban en inglés al compilador:

ADD UNO TRES 5 TO RESULTADO.
ADD UNO TRES 5 GIVING RESULTADO.

La primera línea suma los valores de las variables UNO, TRES y el 5 a la variable RESULTADO, mientras que la segunda suma los tres valores y los asigna a RESULTADO, que no es lo mismo. El “TO RESULTADO” en C++ es el cómodo “resultado +=“, mientras que el “GIVING RESULTADO” es la asignación típica.

Hoy por hoy las cosas no son tan exageradas, claro, pero la mayoría de los lenguajes conservan dentro de sus palabras reservadas una batería de palabras inglesas. De alguna forma los gringos se dieron cuenta de que no tenía sentido hablarle un inglés tan “perfecto” a la máquina para que hiciera su tarea.

A nosotros, los no angloparlantes desde la cuna, no nos preocupa demasiado tener que escribir palabras inglesas mientras programamos. Al fin y al cabo el inglés siempre ha estado ahí con nosotros, y esas palabrejas nos salen de forma más o menos fácil. Otro gallo cantaría si a Strouptrup le hubiera dado por poner las palabras clave de C++ en Danés, o a Guido van Rossum las de Python en Holandés o a Yukihiro Matsumoto las de Ruby en Japonés, obviamente.

Lo curioso en este caso es que, bajo mi punto de vista, los no nativos llegamos a tener cierta ventaja sobre los nativos cuando programamos. Porque si le pasáramos un traductor automático a uno de nuestros programas saldrían cosas como las siguientes (espero otros ejemplos vuestros, si es posible graciosos, en los comentarios…):

C, C++ y amigos

#incluye "esestandard.cab"
real dios, demonio;
entero resultadoParcial;

clase CJugador {
público:
   entero aplaude();
privado:
   clase amiga CEnemigo;
};

estandard::Conjunto *dameCaracteres() {
   vuelve NULO;
}

vacío principal(entero contadorArgs, caracter **args) {
   Cadena *c = nuevo Cadena();

   escribef("%c", c->cadena_c());

   vuelve;
}

Java (que se llamaría Lanzarote, o Fuerteventura…)

importa paquete.grande;

clase pública MiClase {
   vacío estático público principal(Cadena []args) {
      Sistema.salida.EscribeLin("bu?");
   }
};

SQL y amigos

CREA BASEDEDATOS [MisDatos] EN PRIMARIO (...)

CAMBIA BASEDEDATOS [MisDatos] PON
         CREA_ESTADISTICAS_AUTOMATICAS SI

USA [MisDatos]
CREA TABLA [MiTabla] (
      [Clave] [Entero] IDENTIDAD(1, 1) NO VACIO,
      [Nombre] [caracterVariable](50) NO VACIO,
      RESTRICCION [CP_MiTabla] CLAVE PRIMARIA (
      Clave ASCENDENTE
      ) CON ( IGNORA_CLAVES_DUPLCADAS = NO ) EN PRIMARIO
VAMOS

Me encanta. “vacío principal“, “público: entero aplaude();“.

No sé a vosotros, pero a mí esas cosas en el código me despistarían. A priori, tener que utilizar unas palabras de otro idioma puede hacer que aumente la distancia que el desarrollador percibe entre él y la máquina. Sin embargo, en realidad esas palabras en tu cabeza no significan otra cosa que instrucciones dadas al compilador. Al fin y al cabo, cuando yo tecleo “void“, no me viene a la cabeza el vacío interestelar, qué queréis que os diga. Sin embargo, ver en un fichero de código fuente “vacío llenaVaso()“, pues oye, despista.

Y no sólo eso. En realidad, si le hablas a la máquina en tu lenguaje simplificado, la sensación que percibes es que la máquina es idiota. Simple y llanamente. Porque me obliga a decirle “nuevo Cadena();”, en vez de “nueva Cadena();”. El resultado, supongo, es un distanciamiento aún mayor entre hombre y máquina, porque, al fin y al cabo, la máquina me está obligando a que cambie la forma en la que hablo con la gente sólo para que ella me pueda entender. Distancia, distancia y más distancia.

Que se jodan los nativos (con cariño).

Querido Ndugu,

Hoy los programadores de C++ estamos de enhorabuena. El famoso nuevo estándar de C++, conocido entre los amigos como 0x tiene ya el primer borrador completo. El nombre, 0x, era una especie de promesa de la gente del comité que hace años asumió la responsabilidad de tenerlo listo antes de 2010 (la ‘x’ viene a ser el comodín que sería sustituido por el año de publicación, por lo que el estándar tendría la forma C++ 0x y nunca C++ 1x…).

Hace por lo menos dos años que le vengo siguiendo la pista a las reuniones del comité, a las propuestas y a los documentos generados sobre los estudios de las nuevas características que estaban planteándose añadir, y me alegro de que por fin se acerque el momento de poder utilizar el “auto” especialmente en la declaración de variables con tipos parametrizados, los “conceptos” que mejorarán los mensajes de error dados por el compilador, y otro montón de cosas que iban siendo necesarias en C++.

El borrador tiene nada más y nada menos que 1352 páginas (que espero que no tengan en un único documento en Word :) ). Es un documento que merece la pena conservar aunque la misma portada reconoce que son conscientes de que está incompleto y contiene errores. Pero merce la pena, digo, conservarlo al menos hasta que haya otra versión de borrador más moderna, porque puede que, igual que el documento del estándar anterior, haya que pagar por la versión definitiva.

No es que me de por leerme el estándar de C++ en mis momentos ociosos, pero alguna vez he recurrido a él cuando hacía cosas un poco conflictivas. Y siempre queda bien resolver la cuestión con la frase concreta del estándar que te resuelve la duda… si alguien te viene diciendo que no le funciona, le puedes soltar que su compilador no cumple el estándar, y que la culpa no es tuya :)

Siempre he creído que trabajar en un comité de estandarización no debe ser nada fácil. Este borrador lo refleja claramente. Pero por si sois perezosos y ni siquiera abrís el enlace para intentar entender el soporte de las hebras (si pasáis de la primera página de la sección habiéndolo entendido, decidlo :) ), contaré algo yo sobre esto.

En el desarrollo del estándar hay que intentar fijar unas normas de tal forma que sean tan claras que no dejen lugar a dudas sobre lo que quieren decir, ni dejen cabos sueltos que abra las puertas a futuras incompatibilidades entre los implementadores.

A eso hay que añadir la gran cantidad de conflictos de intereses contra los que algunos estándares tienen que luchar, las sugerencias o ideas que reciben los comités, las presiones de tiempo y un largo etcétera.

El libro “The design and evolution of C++” es una buena fuente para darse cuenta de todos estos aspectos, pues describe la experiencia desde dentro (los ejemplos que aquí describo están sacados del libro). Con él se aprende que gran parte del proceso de estandarización consiste en “encontrar la forma de expresar completa y claramente lo que todo el mundo sabe pero resulta no estar escrito en ningún manual”, en aclarar aspectos oscuros que afectan a un pequeño número de programadores, pero que son vitales para los implementadores de los compiladores que en un momento dado se enfrentan al problema de decidir cómo compilar un ejemplo concreto de código.

El libro dedica unas cuántas páginas a resolver el problema de estandarización del método para determinar con exactitud a qué declaración corresponde un nombre cuando se declara una clase. Por ejemplo, ¿a qué x se refiere la implementación de X::f() en el siguiente código?:

int x;

class X {
   int f() { return x; }
   int x;
};

Cualquiera que haya programado un poco en C++ contestará (correctamente) que se refiere a X::x. Podríamos incluso ser capaz de deducir una regla (incorrecta, como veremos) del tipo “en la definición de las clases, los símbolos definidos dentro de ella están disponibles también para las declaraciones anteriores a la propia definición del símbolo”.

Entonces, ¿qué ocurre con este código?

class T {
   A f();
   void g() { A a; /* ... */ }
   typedef int A;
}

Con la regla anterior debería compilar, pues A está definido dentro de la clase T que lo utiliza… pero no. No compila. Y no compila porque en realidad, el primer ejemplo compilaba por otra regla distinta, una cuyo nombre técnico es “regla de reescritura” y que viene a decir que las funciones miembro que se declaran inline deben ser analizadas como si estuvieran definidas inmediatamente después del final de la declaración de la clase. Con esta regla, la implementación de X::f() del primer ejemplo se analiza “después” de analizar toda la clase, y en particular, su atributo X::x.

Y según esta regla, este segundo ejemplo no compila cuando analiza la línea A f(), ya que el tipo A no está definido, pero no da error en la función inline ya que, aplicando la regla de reescritura, al final de la clase A ya está definido.

La regla de reescritura, no obstante, introduce un nuevo problema, a saber: ¿a qué T se refiere Y::f() en el siguiente trozo de código?

typedef char *T;

class Y {
   T f() { T a = 0; return a; }
   typedef int T;
};

El problema surge debido a que la implementación anterior utilizando la regla de reescritura es equivalente a (o mejor dicho, se compila como):

typedef char *T;

class Y {
   T f();
   typedef int T;
};

inline T Y::f() { T a = 0; return a; }

De tal forma que en la definición de Y::f() (dentro de la clase) ésta devuelve un char *, pero en la declaración/implementación posterior, pasa a devolver un entero.

Pues bien, para evitar esto, se introduce una regla más, conocida como la regla de la redefinición del tipo, que dice que un tipo no puede ser redefinido en una clase después de su primer uso en ella. Por lo tanto, el error que da el compilador es algo como “error en la declaración typedef int Y::T, por cambio de significado” (es curioso comprobar que esta regla no se tiene en cuenta en Visual Studio, al menos en las versiones 2003 ni 2005…).

Volviendo al proceso de estandarización, estas dos reglas ya estaban claras cuando el proceso de estandarización de C++ comenzó. En principio, parecían suficientes para cubrir todos los casos pero ahí es donde entra el trabajo de la gente del comité que se dedica a busca casos esotéricos que no sean cubiertos por las reglas. Son fragmentos de código que uno no se encuentra en su quehacer diario pero que, cuando alguien se enfrenta al problema de programar un compilador, debe resolver. Si se utilizan únicamente las dos reglas anteriores, no está claro qué se debe hacer.

El ejemplo que aparece en el libro y que refleja que el trabajo de una de estas personas puede llegar a ser perjudicial para su salud mental es el siguiente (tengo que reconocer que me costó bastante entender qué quería decir…):

typedef int P();
typedef int Q();

class X {
   static P(Q); // equivalente a static P Q; es decir
                // la definición del símbolo Q del tipo
                // P, es decir, función que devuelve un int

                // Q por tanto ya no es el typedef

   static Q(P); // definición de Q como una función que
                // recibe como parámetro a P y que devuelve
                // un entero (int implícito heredado de C).
};

Si obviamos que finalmente eliminaron la posibilidad de omitir el int en el tipo devuelto por una función, el código anterior era válido; “simplemente” declara dos funciones Q con tipos distintos en los parámetros. Sin embargo, si le damos la vuelta a sus declaraciones, estaríamos definiendo dos funciones P. Si quitamos alguno de los typedef tenemos otros significado…

Un ejemplo más sencillo que tampoco era cubierto por las reglas anteriores es:

int b;

class Z {
   static int a[sizeof(b)];
   static int b[sizeof(a)];
};

En este caso, también debería resultar en un error pues b cambia de significado después de su primer uso (aunque no cambia en realidad el significado de ningún tipo).

Espero que estas secciones de código y sus explicaciones hayan sido suficientes para ejemplificar lo duro que debe ser trabajar en uno de estos comités.

Por terminar el post, pongo aquí las reglas que finalmente adoptó el comité en Marzo de 1993:

  1. El ámbito de un nombre declarado en una clase es no sólo el texto que sigue a la declaración de ese nombre, sino también el cuerpo de todas las funciones, argumentos por defecto e inicializadores de constructores en esa clase (y sus clases anidadas). Obviamente se excluye en la propia definición.
  2. Un nombre N utilizado en la declaración de la clase S debe evaluarse de igual manera en el contexto que aparece y en el ámbito completo de S, entendiendo como ámbito completo la clase S, sus clases base y todas las clases que la encierran.
  3. Si reordenando la declaración de miembros conduce a un programa válido bajo las reglas dos reglas anteriores, el comportamiento del programa no está definido.

¿Está incluido en el catálogo de libros de una biblioteca el propio catálogo?

Si M es el conjunto de todos los conjuntos que no se contienen a sí mismo, ¿está M incluido en M?

Si los barberos sólo pueden afeitar a la gente que no sabe afeitarse por sí misma, ¿quién afeita al único barbero del pueblo?

Y para terminar, una más fácil: El manual del usuario del ISBN, ¿tiene ISBN?…

… pues no.

(para las otras preguntas, informarse sobre la Paradoja de Russell)

© 2011 Teclarios Suffusion theme by Sayontan Sinha