Referencia: Este artículo es una tradución al español de “Less is exponentially more” por Rob Pike.
Aquí está el texto de la charla que di en la reunión de Go SF en Junio de 2012.
Esta es una charla personal. No hablo por nadie más del equipo de Go aquí, aunque quiero reconocer desde el principio que el equipo es lo que hizo y sigue haciendo que Go suceda. También me gustaría agradecer a los organizadores del Go SF por darme la oportunidad de hablar con ustedes.
Hace unas semanas me preguntaron: “¿Cuál fue la mayor sorpresa que te encontraste desarrollando Go?” Supe la respuesta al instante: Aunque esperábamos que los programadores C++ vieran a Go como una alternativa, en cambio la mayoría de los programadores Go vienen de lenguajes como Python y Ruby. Muy pocos vienen de C++.
Nosotros, Ken, Robert y yo, éramos programadores de C++ cuando diseñamos un nuevo lenguaje para resolver los problemas que pensábamos que debían ser resueltos para el tipo de software que escribíamos. Parece casi paradójico que a otros programadores de C++ no les importe.
Me gustaría hablar hoy de lo que nos impulsó a crear Go, y por qué el resultado no debería habernos sorprendido de esta manera. Prometo que esto será más sobre Go que sobre C++, y que si no conoces C++ podrás seguirlo.
La respuesta puede resumirse así: ¿Piensas que menos es más, o que menos es menos?
Aquí hay una metáfora, en forma de una historia real. A los centros de los Laboratorios Bell se les asignaron originalmente números de tres letras: 111 para la investigación en física, 127 para la investigación en ciencias de la computación, y así sucesivamente. A principios de los años 80 llegó un memorándum anunciando que a medida que nuestra comprensión de la investigación había crecido, se había hecho necesario añadir otro dígito para poder caracterizar mejor nuestro trabajo. Así que nuestro centro se convirtió en 1127. Ron Hardin bromeó, medio en serio, que si realmente entendíamos mejor nuestro mundo, podíamos dejar caer un dígito y bajar de 127 a sólo 27. Por supuesto que la dirección no entendió la broma, ni se esperaba que lo hiciera, pero creo que hay sabiduría en ello. Menos puede ser más. Cuanto mejor lo entiendas, más concienzudo podrás ser.
Ten esa idea en mente.
Alrededor de Septiembre de 2007, estaba haciendo un trabajo menor pero central en un enorme programa de Google en C++, con el que todos ustedes han interactuado, y mis compilaciones estaban tomando cerca de 45 minutos en nuestro enorme clúster de compilación distribuido. Se anunció que habría una charla presentada por un par de empleados de Google que formaban parte del comité de estándares de C++. Iban a decirnos lo que venía en C++0x, como se llamaba en ese momento. (Ahora se conoce como C++11).
En el lapso de una hora en esa charla escuchamos algo así como 35 nuevas características que estaban siendo planeadas. De hecho había muchas más, pero sólo 35 fueron descritas en la charla. Algunas de las características eran menores, por supuesto, pero las de la charla eran al menos lo suficientemente significativas como para llamarlas. Algunas eran muy sutiles y difíciles de entender, como las referencias de valores, mientras que otras son especialmente parecidas a C++, como las plantillas variadas (variadic templates), y otras son simplemente locas, como los literales definidos por el usuario.
En este punto me hice una pregunta: ¿Realmente el comité de C++ creía que lo malo de C++ era que no tenía suficientes características? Seguramente, en una variante de la broma de Ron Hardin, sería un logro mayor simplificar el lenguaje en lugar de añadirle. Por supuesto, eso es ridículo, pero mantén esa idea en mente.
Sólo unos meses antes de esa charla de C++ yo mismo había dado una charla, que puedes ver en YouTube, sobre un lenguaje concurrente de juguete que había construido allá por los años 80. Ese lenguaje se llamaba Newsqueak y por supuesto es un precursor de Go.
Di esa charla porque había ideas en Newsqueak que me faltaban en mi trabajo en Google y había estado pensando en ellas de nuevo. Estaba convencido de que facilitarían la escritura de código de servidor y Google podría beneficiarse de ello.
De hecho, intenté y fallé en encontrar una manera de llevar las ideas a C++. Era demasiado difícil acoplar las operaciones concurrentes con las estructuras de control de C++, y a su vez eso hacía demasiado difícil ver las ventajas reales. Además, C++ sólo hizo que todo pareciera demasiado engorroso, aunque admito que nunca fui realmente indulgente con el lenguaje. Así que abandoné la idea.
Pero la charla de C++0x me hizo pensar de nuevo. Una cosa que realmente me molestó, y creo que a Ken y Robert también, fue el nuevo modelo de memoria C++ con tipos atómicos (atomic types). Me pareció mal poner un conjunto de detalles tan microscópicamente definidos en un sistema de tipos ya sobrecargado. También parecía miope, ya que es probable que el hardware cambie significativamente en la próxima década y no sería prudente acoplar el lenguaje demasiado estrechamente al hardware actual.
Volvimos a nuestras oficinas después de la charla. Empecé otra compilación, giré mi silla para enfrentarme a Robert, y empecé a hacerle preguntas directas. Antes de que la compilación estuviera terminada, habíamos atado a Ken y habíamos decidido hacer algo. No queríamos estar escribiendo en C++ para siempre, y nosotros, yo especialmente, queríamos tener la concurrencia al alcance de la mano al escribir el código de Google. También queríamos abordar el problema de “programar en grande” de frente, sobre lo cual más adelante se detalla.
Escribimos en la pizarra un montón de cosas que queríamos, Desiderata si quieres. Pensamos en grande, ignorando la sintaxis, la semántica detallada y centrándonos en el panorama general.
Todavía tengo un fascinante hilo de correo de esa semana. Aquí hay un par de extractos:
Robert: Punto de partida: C, arreglar algunos defectos obvios, quitar la suciedad, añadir algunas características que faltan.
Rob: nombre: ‘go’. puedes inventar razones para este nombre pero tiene buenas propiedades. Es corto, fácil de escribir. herramientas: goc, gol, goa. Si hay un depurador/intérprete interactivo podría llamarse ‘go’. El sufijo es .go.
Robert, Interfaces vacías: interface{}. Estas son implementadas por todas las interfaces, y por lo tanto esto podría tomar el lugar de void.*
No nos dimos cuenta de todo de inmediato. Por ejemplo, nos llevó más de un año descubrir los arreglos y los cortes. Pero una cantidad significativa del sabor del lenguaje surgió en ese primer par de días.
Noten que Robert dijo que C era el punto de partida, no C++. No estoy seguro, pero creo que se refería a C propiamente dicho, especialmente porque Ken estuvo allí. Pero también es cierto que, al final, no empezamos realmente desde C. Construimos desde cero, tomando prestadas sólo cosas menores como operadores y paréntesis y algunas palabras clave comunes. (Y por supuesto también tomamos prestadas ideas de otros lenguajes que conocíamos.) En cualquier caso, ahora veo que reaccionamos a C++ volviendo a lo básico, desglosando todo y empezando de nuevo. No tratábamos de diseñar un mejor C++, o incluso un mejor C. Iba a ser un lenguaje mejor en general para el tipo de software que nos importaba.
Al final, por supuesto, salió bastante diferente de C o C++. Más diferente incluso de lo que muchos se dan cuenta. Hice una lista de simplificaciones significativas en Go sobre C y C++:
- sintaxis regular (no necesita una tabla de símbolos para analizar)
- recolección de basura (solamente)
- no hay archivos de cabecera
- dependencias explícitas
- no hay dependencias circulares
- las constantes son sólo números
- int e int32 son tipos distintos
- letter case asigna visibilidad
- métodos para cualquier tipo (sin clases)
- no hay herencia de subtipos (no hay subclases)
- Inicialización a nivel de paquete y orden de inicialización bien definido
- archivos compilados juntos en un paquete
- globales a nivel de paquete presentados en cualquier orden
- no hay conversiones aritméticas (las constantes ayudan)
- las interfaces están implícitas (no hay declaración “implements”)
- incrustación o embedding (no hay ascenso a la superclase)
- los métodos se declaran como funciones (sin ubicación especial)
- los métodos son sólo funciones
- las interfaces son sólo métodos (sin datos)
- los métodos coinciden sólo por el nombre (no por el tipo)
- no hay constructores ni destructores
- El postincremento y el postdecrecimiento son declaraciones, no expresiones
- no hay preincremento o predecremento
- la asignación no es una expresión
- orden de evaluación definida en la asignación, llamada de función (no “punto de secuencia”)
- no hay puntero aritmético
- la memoria siempre se mantiene a cero
- legal tomar la dirección de la variable local
- no hay “this” en los métodos
- stacks segmentados
- no hay anotaciones de tipo const o de otro tipo
- no hay plantillas
- sin excepciones
- soporte incorporado para string, slice, map
- comprobación de los límites de la matriz
Y aún así, con esa larga lista de simplificaciones y piezas faltantes, Go es, creo, más expresivo que C o C++. Menos puede ser más.
Pero no se puede sacar todo. Necesitas bloques de construcción como una idea sobre cómo se comportan los tipos, y una sintaxis que funcione bien en la práctica, y alguna cosa inefable que haga que las bibliotecas interoperen bien.
También añadimos algunas cosas que no estaban en C o C++, como slices, maps, literales compuestos, expresiones en el nivel superior del archivo (que es una cosa enorme que en su mayoría no se marca), reflexión, recolección de basura, y así sucesivamente. Concurrencia, también, naturalmente.
Una cosa que está notoriamente ausente es, por supuesto, una jerarquía de tipos. Permítanme ser grosero sobre eso por un minuto.
Al principio del lanzamiento de Go, me dijo alguien que no podía imaginar trabajar en un lenguaje sin tipos genéricos. Como he informado en otra parte, encontré ese comentario extraño.
Para ser justos, probablemente estaba diciendo a su manera que le gustaba mucho lo que la STL hace por él en C++. Para el propósito del argumento, sin embargo, tomemos su afirmación al pie de la letra.
Lo que dice es que encuentra que los contenedores de escritura como listas de enteros y maps de strings son una carga insoportable. Me parece una afirmación extraña. Paso muy poco de mi tiempo de programación luchando con esos temas, incluso en lenguajes sin tipos genéricos.
Pero lo más importante, lo que dice es que los tipos son la forma de levantar esa carga. Tipos. No funciones polimórficas o los primitivos del lenguaje o helpers de otros tipos, sino tipos.
Ese es el detalle que se me queda grabado.
Los programadores que vienen a Go desde C++ y Java extrañan la idea de programar con tipos, particularmente la herencia y la subclasificación y todo eso. Tal vez sea un filisteo de los tipos, pero nunca encontré ese modelo particularmente expresivo.
Mi difunto amigo Alain Fournier me dijo una vez que consideraba la taxonomía como la forma más baja de trabajo académico. ¿Y sabes qué? Las jerarquías de tipos son sólo taxonomía. Tienes que decidir qué pieza va en cada caja, cada tipo de padre, si A hereda de B o B de A. ¿Una matriz clasificable es una matriz que clasifica o un clasificador representado por una matriz? Si crees que los tipos abordan todas las cuestiones de diseño debes tomar esa decisión.
Creo que es una forma absurda de pensar en la programación. Lo que importa no son las relaciones ancestrales entre las cosas sino lo que pueden hacer por ti.
Ahí, por supuesto, es donde las interfaces vienen a Go. Pero son parte de un panorama más amplio, la verdadera filosofía de Go.
Si C++ y Java tratan sobre jerarquías de tipos y la taxonomía de los tipos, Go trata sobre la composición.
Doug McIlroy, el eventual inventor de los pipes Unix, escribió en 1964 (!):
Deberíamos tener algunas formas de acoplar programas como la manguera del jardín… Esta es la forma de IO también.
Ese es el camino de Go también. Go toma esa idea y la lleva más lejos. Es un lenguaje de composición y acoplamiento.
El ejemplo obvio es la forma en que las interfaces nos dan la composición de los componentes. No importa lo que sea esa cosa, si implementa el método M puedo dejarlo aquí.
Otro ejemplo importante es la forma en que la concurrencia nos da la composición de los cálculos de ejecución independiente.
Y hay incluso una forma inusual (y muy simple) de composición de tipo: incrustación (embedding).
Estas técnicas de composición son las que le dan a Go su sabor, que es profundamente diferente del sabor de los programas de C++ o Java.
===========
Hay un aspecto no relacionado con el diseño de Go que me gustaría tocar: Go fue diseñado para ayudar a escribir grandes programas, escritos y mantenidos por grandes equipos.
Hay una idea sobre “programar en grande” y de alguna manera C++ y Java son dueños de ese dominio. Creo que eso es sólo un accidente histórico, o quizás un accidente industrial. Pero la creencia más extendida es que tiene algo que ver con el diseño orientado a objetos.
No me lo creo en absoluto. El gran software necesita una metodología para estar seguro, pero no tanto como necesita una fuerte administración de dependencia y una abstracción de interfaz limpia y magníficas herramientas de documentación, ninguna de las cuales está bien servida por C++ (aunque Java lo hace notablemente mejor).
No lo sabemos todavía, porque no se ha escrito suficiente software en Go, pero confío en que Go resultará ser un magnífico lenguaje para la programación en los grandes. El tiempo lo dirá.
===========
Ahora, volviendo a la sorprendente pregunta que abrió mi charla:
¿Por qué Go, un lenguaje diseñado desde la base para lo que se usa en C++, no atrae a más programadores C++?
Bromas aparte, creo que es porque Go y C++ son profundamente diferentes filosóficamente.
C++ se trata de tenerlo todo a tu alcance. Encontré esta cita en una FAQ de C++11:
La gama de abstracciones que C++ puede expresar con elegancia, flexibilidad y sin costo alguno en comparación con el código especializado hecho a mano ha aumentado considerablemente.
Esa forma de pensar no es la forma en que opera Go. El coste cero no es un objetivo, al menos no el coste cero de la CPU. La afirmación de Go es que minimizar el esfuerzo del programador es una consideración más importante.
Go no lo abarca todo. No lo tienes todo incorporado. No tienes un control preciso de cada matiz de ejecución. Por ejemplo, no tienes RAII. En cambio, tienes un recolector de basura. Ni siquiera tienes una función de liberación de memoria.
Lo que se te da es un conjunto de bloques de construcción poderosos pero fáciles de entender y usar, a partir de los cuales puedes ensamblar -componer- una solución a tu problema. Puede que no termine tan rápido o tan sofisticado o tan ideológicamente motivado como la solución que escribirías en algunos de esos otros lenguajes, pero casi seguro que será más fácil de escribir, más fácil de leer, más fácil de entender, más fácil de mantener, y tal vez más seguro.
Para decirlo de otra manera, simplificando demasiado, por supuesto:
Los programadores de Python y Ruby vienen a Go porque no tienen que renunciar a mucha expresividad, sino que ganan rendimiento y consiguen jugar con la concurrencia.
Los programadores de C++ no vienen a Go porque han luchado duro para obtener un control exquisito de su dominio de programación, y no quieren renunciar a nada de eso. Para ellos, el software no se trata sólo de hacer el trabajo, sino de hacerlo de cierta manera.
La cuestión, entonces, es que el éxito de Go contradice su visión del mundo.
Y deberíamos habernos dado cuenta de eso desde el principio. La gente que está entusiasmada con las nuevas características de C++11 no se va a preocupar por un lenguaje que tiene mucho menos. Incluso si, al final, ofrece mucho más.
Gracias.
Rob Pike - Less is exponentially more