Featuritis en lenguajes de programación

La semana pasada se publicaron las notas sobre la primera reunión de diseño de C# 7 (sí, aún no está completa la versión 6 pero ya se está trabajando en lo que será la siguiente versión). Todavía es muy pronto para saber en qué se acabará convirtiendo C# 7, pero entre las características que se están valorando hay algunas de las que no pasaron el corte para C# 6 y que me parecen realmente interesantes, como el pattern matching, las estructuras de datos inmutables o las referencias no nullables.

Sin embargo, no quiero centrar este post en las (posibles) mejoras que nos traerá C# 7, sino en otro tema que cada vez es más frecuente: la acumulación de características en los lenguajes de programación.

Lenguajes cada vez más completos/complejos

Como bien decía Eduard Tomás en twitter, parece que la tendencia actual es añadir más y más funcionalidades a los lenguajes para facilitar el uso de distintos estilos de programación.

Hace unos cuantos años lo habitual era que un lenguaje fuese orientado a objetos, o funcional, o lógico, pero hoy en día incluso los lenguajes más conservadores, como Java, mezclan varios de estos paradigmas. Esto tiene unas ventajas claras porque nos permite utilizar un mismo lenguaje para resolver distintos tipos de problemas usando el enfoque más adecuado en cada caso, pero también tiene algunos inconvenientes asociados.

Para añadir soporte para todos estos escenarios, la mayoría de los lenguajes (aquí LISP y sus derivados son una excepción) necesitan crear nuevas construcciones sintácticas, que en ocasiones implican cambios reales en la semántica del lenguaje, pero que en muchas ocasiones son únicamente azucar sintáctico que el compilador se encarga de traducir a construcciones más básicas del lenguaje. Es decir, no se trata tanto de hacer cosas nuevas, sino de hacer más cómodamente cosas viejas.

En el caso de C# (que es el lenguaje que más y mejor conozco), tenemos ejemplos claros de esto en las enumeraciones con yield, las funciones lambda o el uso de async/await. En todos estos casos es el compilador el que traduce estas construcciones a clases normales y corrientes para simplificar el uso de estas técnicas.

Todo esto está muy bien, al fin y al cabo lo que hace es facilitarnos la vida a los programadores, pero también introduce una complejidad adicional a la hora de comprender el lenguaje y estas «mejoras» no siempre acaban siendo muy utilizadas (¿cuántos habéis usado métodos parciales en C#? O mejor, ¿cuántos sabéis lo que son los métodos parciales en C#?).

Llevando esta idea al extremo tenemos lenguajes como Scala o Kotlin, en los que existe una sintaxis específica para hacer casi cualquier cosa que te puedas imaginar, pero en los que a veces al comparar un proyecto con otro parece que estas viendo lenguajes diferentes.

En el otro extremo, tenemos lenguajes como Go, que tiene como uno de sus objetivos fundamentales mantener una sintaxis sencilla y tener «pocas funcionalidades». Se trata de una decisión consciente de los diseñadores del lenguaje para mantenerlo simple, y su argumento es que no merece la pena añadir azucar sintáctico para cosas que, en realidad, se pueden hacer en unas pocas líneas de código ganando (supuestamente) en claridad y obteniendo un código más explícito.

Preferir una opción a otra no deja de tener un componente subjetivo importante y, simplificando mucho el asunto, podríamos verlo como un balance entre expresividad y simplicidad. Yo, personalmente, a día de hoy me inclino más por la expresividad, pero comprendo la otra postura y conozco a defensores de Go que no cambiarían su lenguaje por uno con características más avanzadas.

En el fondo, con los lenguajes de programación acaba ocurriendo como con la mayoría de aplicaciones informáticas, tienden a crecer en funcionalidades y pueden acabar colapsados por ellas. Se pueden hacer excesivamente complicados de utilizar por la abundancia de opciones y, sobre todo, porque la intersección de las distintas funcionalidades dé lugar a situaciones poco previsibles.

Extender sin modificar el lenguaje

Para añadir nuevas opciones a un lenguaje existen alternativas que no implican modificar el propio lenguaje.

Es posible intentar delegar estas nuevas funcionalidades en librerías, de manera que no haya que tocar «el núcleo» del lenguaje y que la inclusión de las nuevas características sea opcional. Esto es frecuente en lenguajes como Clojure, donde existen librerías para utilizar pattern matching, comunicación asíncrona al estilo go, o incluso programación lógica.

Utilizando esta aproximación es posible añadir funcionalidades al lenguaje sin complicarlo en exceso, aunque es cierto que hay lenguajes que soportan esta idea mejor que otros. Además, esto no evita el «problema» de perder, en cierto modo, familiaridad con el lenguaje, ya que dependiendo de las librerías que se utilicen en cada proyecto, el aspecto del mismo puede variar sustancialmente.

Otra forma de afrontar el problema de añadir funcionalidades a un lenguaje sin sobrecargarlo es, directamente, utilizar otro lenguaje. Puede sonar raro, pero en realidad es algo bastante frecuente y lo hacemos continuamente, integrando dentro de un sistema partes escritas en distintos lenguajes.

Si la interoperabilidad entre lenguajes está bien resuelta, y en esto tanto .NET como la JVM tienen bastante ganado, es una opción viable que puede dar buenos resultados. Tampoco se trata de una alternativa exenta de problemas, porque saltar entre lenguajes implica un cambio de contexto y eso supone un coste cognitivo adicional.

A cambio, permite que mientras estamos trabajando con cada lenguaje podamos centrarnos en la forma «canónica» de usarlo y puede ayudarnos a comprender mejor cómo resolver cada problema.

Conclusiones

No soy ningún experto en diseño de lenguajes y no sé cuál es la mejor forma de resolver el problema, pero creo que añadir a tu lenguaje favorito todas las características que se te ocurran no parece la mejor salida.

Encontrar un equilibrio entre expresividad y simplicidad es complicado, y las alternativas, como la extensión mediante librerías o la integración con otros lenguajes, aunque tienen sus ventajas, tampoco están exentas de problemas.

Lo bueno es que siempre habrá lenguajes que opten por tener muchas funcionalidades y otros que prefieran mantener una filosofía más simple. Cada cual que elija lo que más le guste.

4 comentarios en “Featuritis en lenguajes de programación

  1. Una de las innumerbles citas de Alan Perlis trata el tema:
    «El azucar sintactica causa cancer de semicolon»
    Uno de los motivos de la featuritis es el mismo que lo provoca en los frameworks o aplicaciones: el marketing. Para mantener el interes y aumentar la base de usuarios es necesario sacar versiones atractivas al «mercado» que compitan con otros «productos» similares, a veces siendo presas de modas pasajeras (un caso paradigmantico es el soporte nativo para la sintaxis xml de scala). Esto entra en conflicto con la necesidad de mantener la compatibilidad hacia atras de los lenguajes mas populares, con lo que las nuevas caracteristicas son metidas a calzador y se limitan al azucar sintactica (ver por ejemplo la implementacion de las funciones en la jdjk).
    Para mi clojure es un caso opouesto a esta tendencia: la idea de Rick Hickey fue crear de partida un lenguaje simple y extensible (de ahi la eleccion de lisp) y es muy, muy reacio a meter nuevas caracteristicas en el lenguaje. Casi todas las mejoras consisten en meter nuevas librerias en el core «extendido», y son opcionales (core.typed, core.match, core.logic, core.async, etc, etc).
    Los cambios de calado van en la direccion de aumentar el rendimiento, actuando en el plano semantico mas que en el meramente sintactico (que en lisp es secundario). Cambios como los protocolos o el poner por defecto una aritmetica con overflow de tipos numericos son un ejemplo.
    La discusion con Steve Yegge, que recibio clojure con alborozo en sus comienzos, sobre la pasada unica del compilador de clojure (que hace que no puedas declarar elementos posteriormente a su uso) fue un hito en ese sentido:
    https://groups.google.com/forum/#!msg/seajure/GLqhj_2915A/E0crn6zHLi0J
    Otro fue la discusion acerca de que los tipos numericos no se autopromocionaran a tipos mas grandes (de integer a BigInteger pe.e.)
    https://groups.google.com/d/msg/clojure/yMhQWVyRzBE/V4dRqsI3xWoJ

    Clojure perdio a muchos seguidores iniciales debido a ese conservadurismo de Hickey. El brillo de las nuevas caracteristicas sigue atrayendo a mas usuarios, y como consumidores somos los culpables de esa tendencia del mercado.

    Haskell es otro caso diferente, su evolucion esta igual o mas dominada por la investigacion academica que por el «mercado» y tiene el mecanismo de los pragmas de lenguaje para activas caracteristicas especiales fuera del nucleo, que se ha mantenido realtivamente estable a lo largo de su historia (de ya 25 años).

  2. Reconozcámoslo, siendo optimistas y usando como analogía los sistemas de numeración, nuestros lenguajes de programación son como los números romanos, nos apañamos con ellos, hacemos grandes cosas con ellos (acueductos e impresionantes foros), pero nadie tiene ni idea de cómo mejorarlos.

    Estamos estancados.

    La fuerza del populismo (coincido con @jneira), la necesidad de destacar de los demás y/o el afán de avanzar (aunque no se sepa como) hacen que estemos inundados de nuevos lenguajes, paradigmas (no sólo langs) y «featuritis».

    Hasta que no se invente el sistema decimal de la programación, seguiremos en tinieblas.

    Para mí (pobre granito de arena en la inmensidad del desierto) hay dos caminos (¿otros?):

    1. la estandarización y consolidación de tecnologías (como rara vez ocurre, véase Ethernet, HTTP, SMTP, …) que hace que *realmente* avancemos en la productividad aunque no sean las mejores y que, en cuanto a lenguajes está completamente eclipsado por el efecto anterior comentado.

    2. el análisis formal de los lenguajes que muuuuuy poco a poco aportan resultados, aun claramente insuficientes para establecer una *base sólida* sobre la que crecer (aka un buen sistema de numeración).

    Más allá de lo divertido de alparcear sobre lenguajes de programación, el panorama hace tiempo es desolador…

    NOTA: creo que por eso me gusta Haskell, permite vislumbrar (aunque quizás sea un vano reflejo) ese posible futuro.

  3. Yo la parte de que surgan nuevos lenguajes, paradigmas, técnicas o lo que sea, no lo veo como algo negativo, precisamente por lo que comenta @josejuan, todavía estamos en pañales y es necesario experimentar e investigar nuevos caminos.

    La parte que me gusta menos es intentar acumular en un mismo lenguaje todas las novedades. En esto coincido con vosotros en que hay un componente de marketing importante. En el enlace de @jneira de Steve Yegge y clojure se ve bastante bien, con la insistencia de añadir features para llegar a cuantos más desarrolladores mejor.

    En ese sentido, estandarizar y mejorar la productividad con soluciones «good enough» (no necesariamente las mejores, ni las más modernas) creo que tiene bastante sentido. Por ejemplo C no ha evolucionado mucho en los últimos 40 años, y se sigue usando precisamente por eso: resulta familiar para mucha gente y es «lo bastante bueno» para muchos desarrollos.

  4. Soy un poco mas optimista, me da la sensacion que el resurgir de la programacion funcional (¿tipada?) puede ser la oportunidad para que se extienda la percepcion de que la base de la programacion deben estar en conceptos matematico/logicos formales y no en metaforas cuasiliterarias (como la oop) : en la ingenieria y no en la artesania.
    Los nuevos lenguajes que van apareciendo lo tienen en cuenta cada vez mas: scala, f#, clojure, rust, swift, etc (go tal vez sea la oveja negra en ese sentido). Para los lenguajes existentes se estan creando herramientas para poder usarlos mas decentemente (javascript o php)

Comentarios cerrados.