Inicio / Artículos / Por qué la teoría de compiladores sigue importando cuando la IA escribe el código por ti

Por qué la teoría de compiladores sigue importando cuando la IA escribe el código por ti

La IA genera un parser en minutos, pero no te dirá cuándo necesitas uno. Cómo los lenguajes pequeños y las reglas deterministas ganan a los prompts, y por qué el cuello de botella eres tú.

1253 palabras

Una IA puede escribir un compilador por encargo en pocos minutos. ¿Significa eso que la teoría de compiladores está acabada como asignatura? Para nada. Construirlo nunca fue la parte difícil. Lo difícil es darte cuenta de que lo necesitas, y un modelo no te lo va a decir.

Una discusión antigua con una excusa nueva

Las universidades llevan tiempo recortando los cursos de compiladores de sus planes de estudio. El argumento en contra suena razonable: escribir un compilador es un conocimiento de nicho, necesario solo en unos pocos puestos, así que la mayoría de los programadores nunca escribirá uno en toda su carrera.

La réplica es que escribir un compilador de C o JavaScript es una cosa, y todo lo que la teoría de compiladores te permite hacer es otra. Tratarla como algo útil solo para crear lenguajes de programación es quedarse corto. Las mismas ideas están detrás del procesamiento de texto exigente, y además hacen que el código sea más simple y fácil de leer.

Ahora la IA ha dado nuevos argumentos a los escépticos. Un modelo conoce toda la teoría y te genera el código de parsing, así que ¿para qué aprender nada? ¿Es un argumento de verdad o solo una excusa?

Usos por los que nadie te pregunta

Pensemos en un pequeño ejercicio de calculadora para estudiantes, en el que los tests vienen dados y el alumno implementa el comportamiento. Hay decenas de casos, y cada test se parece a esto:

test('Simple addition', () => {
  const calculator = new Calculator();
  calculator.press(4);
  calculator.addition();
  calculator.press(2);
  calculator.equals();
  expect(calculator.getDisplay()).equals('6');
});

Aislado se lee bien, pero es tedioso de seguir. Multiplícalo por cuarenta y pierdes la cuenta de qué está cubierto y qué no. Con un poco de mentalidad de compilador puedes escribir un único helper que hace de usuario y lo comprueba todo, de modo que un test se reduce a una línea:

test('Simple addition', () => {
  runCase('4 + 2 = [6]');
});

Es mucho más legible. Y se puede ir más lejos. En lugar de cuarenta bloques de test puedes escribir una tabla en la que cada línea es un caso, con lo que debe mostrar la pantalla entre corchetes y un comentario que dice qué demuestra:

test.each([
  '[0]                # display starts at zero',
  '1 [1]              # pressing a digit shows it',
  '12 [12]            # digits concatenate',
  '4 + 2 = [6]        # a basic sum',
  '5 × 3 = [15]       # product works the same',
  '7 - 9 = [-2]       # negative results',
  '9 ÷ 0 = [Error]    # division by zero errors',
  '2 + 3 × 4 = [14]   # respects precedence',
])('%s', runCase);

Aquí se han recortado algunos casos, pero se nota la diferencia. Imagina revisar la misma cobertura escrita caso por caso.

Otro sitio donde esto compensa es al conectar dependencias entre módulos. En código escribirías algo así:

const system = new System();
system.createModule('A');
system.createModule('B');
system.createModule('C');
system.createModule('D');
system.connect('A', 'B');
system.connect('A', 'C');
system.connect('B', 'C');
system.connect('C', 'D');

Con un mínimo de diseño de lenguajes escribirías esto en su lugar:

const system = compileSystem(`
  A -> B -> C -> D
  A      -> C
`);

Con la primera versión a menudo necesitas lápiz y papel para reconstruir el grafo en la cabeza. Con la segunda, simplemente ves el grafo. Implementarlo es casi vergonzosamente sencillo: dividir en líneas y luego dividir cada línea por la flecha.

Hay muchos más ejemplos, y a veces ni siquiera hace falta escribir el compilador porque ya existe uno. Mucha configuración puede ser simplemente YAML. Un sistema de entidades y componentes como los habituales en los videojuegos crece y se adapta con facilidad cuando las entidades se describen así:

goblin:
  extends: enemy
  health: 30
  damage: 5
  ai: aggressive
  drops: [gold, dagger]
  resistances: [poison]

Antes de seguir, pregúntate cuántas de estas ideas te propone un modelo por iniciativa propia cuando le pides código. En la práctica, casi ninguna.

La IA lo hace todavía más necesario

Podrías decir que la IA es el compilador definitivo: entiende casi cualquier lenguaje y puede interpretarlo. Hay una pega. No siempre acierta. Es uno de los mayores problemas para cualquiera que automatice trabajo repetitivo con IA, porque el resultado no es determinista. Hoy hace bien el trabajo y mañana lo deja todo hecho un desastre.

Así que acabas trabajando entre dos tipos de lenguaje muy distintos. El lenguaje natural es como hablas con la IA. El código es como diriges a la IA, o incluso como la ayudas. Si hay que cambiar la parte del código, o se lo pides a la IA o estudias lo que escribió.

No tiene por qué ser así. Fíjate en Gherkin. Se lee como inglés natural, pero cada frase corresponde a una acción preprogramada que hace una cosa concreta. Se creó para las pruebas y para que la gente pudiera leer cómo se comporta un sistema. Como cada frase está respaldada por código real, es determinista y se ejecuta igual cada vez:

Scenario: User withdraws cash from ATM
  Given john's balance is $200
  When john withdraws $100 from the ATM
  Then john's balance should be $100

Tener un lenguaje así para dar instrucciones a una IA sería estupendo, pero no hay motivo para quedarse en la sintaxis de pruebas. Gherkin sirve a los tests, y los tests importan a cualquier negocio, pero los negocios necesitan más que tests. Puedes tener reglas escritas de una forma más cercana a tu dominio o más simple que el código. Reglas de negocio, límites de seguridad y especificaciones escritas vivas encajan aquí, cada una adaptada a tus necesidades. Se leen de un vistazo, son fáciles de cambiar y conservan el determinismo.

Piensa en ello como un tercer lenguaje: ni código ni lenguaje natural, pero determinista. Podría tener este aspecto:

rule "refund window":
  when order.status is "delivered"
  and days_since(order.delivered_at) <= 30
  then allow refund
  else deny "refund window expired"

guardrail "no medical advice":
  if response mentions [diagnosis, dosage, prescription]
  then block and escalate to human

La IA construiría y mantendría la maquinaria que lo ejecuta, e incluso podría ayudarte a diseñar el lenguaje que necesita tu problema. Pero, otra vez: ¿con qué frecuencia te lo ofrece?

El cuello de botella eres tú

Eso nos devuelve a la tesis. ¿Cuántas de estas opciones sugiere un modelo por su cuenta? Normalmente, ninguna.

La IA tiene una potencia asombrosa y un gran cuerpo teórico detrás, y nada de eso sirve si no sabes cómo ponerlo a trabajar. Es una situación curiosa. Aunque el modelo conozca la teoría, no puedes usarla si no sabes que existe.

La buena noticia es que ya no necesitas toda la teoría. Basta con saber qué es posible y cómo podría ayudar. La habilidad ha pasado de saber cómo hacer algo a saber qué pedir. Una vez que lo sabes, puedes aprovechar todo lo que sabe el modelo.

A un prompt de distancia

Así que la idea de las universidades de eliminar los cursos de compiladores sigue siendo mala. Quizá la profundidad no tenga que ser la de antes, pero los estudiantes deben saber que la materia existe. Curiosamente, la propia IA depende de que sepas qué es posible.

Lo mismo pasa en muchos otros campos. Un modelo guarda una cantidad enorme de conocimiento, gran parte enterrado en su espacio latente, esperando el prompt adecuado que lo despierte. Todo está a un prompt de distancia, siempre que sepas qué pedir.