Evoluerende programmeertalen in het AI-tijdperk
Deze post is een verzameling korte overpeinzingen over hoe programmeertalen kunnen evolueren in het tijdperk van AI.
De tekst is verdeeld in twee delen: Reflecties en Agent-gestuurde tooling. Het eerste deel stelt vragen over wat er gebeurt met programmeertalen, hun ecosystemen en hun communities wanneer mensen niet langer het grootste deel van de code schrijven. Het tweede deel is concreter en uitgesproken: hoe onze tools zouden moeten verbeteren nu coding-agents gebruikers van onze talen zijn geworden.
Mijn mening over deze onderwerpen zal waarschijnlijk veranderen, maar dit is een redelijke samenvatting van waar ik de laatste tijd over nadenk.
Reflecties
Deze sectie onderzoekt hoe programmeertalen en hun communities zouden kunnen veranderen als agenten het grootste deel van onze code schrijven. Hoewel dit een controversieel onderwerp blijft, is het voor veel ontwikkelaars en teams al de realiteit. Daarom is het onze verantwoordelijkheid om te onderzoeken hoe deze verschuiving ons kan beïnvloeden, ongeacht hoe wijdverspreid we denken dat het zal worden.
Over de community
In het centrum van de meeste programmeertalen staat een community die zich verenigt rond een set gedeelde principes. Python legt de nadruk op een overduidelijke manier om iets te doen. Ruby heeft al lang een waardering gekweekt voor het geluk van de programmeur. Lisp-communities hebben traditioneel de mogelijkheid gevierd om de taal zelf te hervormen.
Maar wat gebeurt er als we stoppen met het schrijven van het grootste deel van de code? En hoe zal dat onze gevoelens van verbondenheid beïnvloeden? Is dat iets wat we moeten proberen te behouden, of moeten communities ontdekken wat het volgende element zal zijn dat hen samenbindt?
In dezelfde lijn bouwen we ecosystemen rond talen om moeilijke problemen aan te pakken en gedeelde abstracties te creëren: webframeworks, tensor-bibliotheken, data-processing pipelines, GUI-toolkits, enzovoort. Coding-agents kunnen deze ecosystemen op twee tegengestelde manieren beïnvloeden.
Ten eerste kan het gat tussen ecosystemen kleiner worden. Het bouwen van al deze frameworks vereist een aanzienlijke hoeveelheid tijd en moeite, waarvan agenten een groot deel kunnen inkorten. Dit geldt vooral wanneer het probleem gaat om het implementeren van bekende algoritmen, het vertalen van ideeën uit papers of het porteren van bestaande implementaties tussen talen, waardoor kleinere communities veel sneller kunnen inhalen bij grotere.
Aan de andere kant: als het implementeren van iets goedkoop genoeg wordt, zullen mensen dan nog steeds hun krachten bundelen en samenwerken aan dezelfde oplossing? Als ik een bibliotheek nodig heb om probleem X op te lossen, vraag ik misschien gewoon een agent om precies te bouwen wat ik nodig heb.
Dit creëert een interessante spanning. Coding-agents zouden de kosten voor het bouwen van een ecosysteem drastisch kunnen verlagen, terwijl ze tegelijkertijd een van de krachten verzwakken die ervoor zorgen dat ecosystemen in de eerste plaats ontstaan.
Over ergonomie
Een aanzienlijk deel van de evolutie van programmeertalen verloopt via het toevoegen van syntactische hulpmiddelen en het verbeteren van de ergonomie in de loop van de tijd. Echter, als mensen niet langer het grootste deel van de code schrijven, in hoeverre doen deze wijzigingen er dan nog toe?
Kijk bijvoorbeeld naar het afgelopen decennium: we zagen een instroom van talen die optionele chaining operators toevoegden, wat aanzienlijk prettiger is voor een mens om te schrijven dan een reeks expliciete null-checks. Agenten worden echter niet gehinderd door boilerplate-code en het verschil is voor hen veel minder betekenisvol.
Er is een argument dat deze hulpmiddelen coding-agents ook token-efficiënter maken. Maar ik zou willen aanvoeren dat token-efficiëntie aan het einde van de lijst staat van eigenschappen waar we programmeertalen voor moeten optimaliseren, zeker nu modellen goedkoper en efficiënter worden en context-windows groeien.
Ik zou zelfs durven zeggen dat elke nieuwe programmeertaal die claimt gemaakt te zijn "voor coding-agents" en zich uiteindelijk richt op syntaxis, effectief bouwt rond de beperkingen van vandaag. Ik heb agenten gebruikt om HTML, CSS, JavaScript, Elixir, Rust en Lean te schrijven, en de verschillen in syntaxis die voor mij enorm voelen, lijken voor hen aanzienlijk minder belangrijk. Vanuit hun perspectief is het allemaal tokens-in, tokens-out.
Over compilers
Wanneer we programmeertalen in deze context bespreken, is er vaak een vervolgvraag: hebben we überhaupt nog wel programmeertalen nodig? Misschien vervangen coding-agents compilers wel en schrijven ze direct in assembly?
Ik geloof hier niet in om een paar redenen.
Ten eerste: als je een desktopapplicatie bouwt, wil je waarschijnlijk geen verschillende assembly-implementaties onderhouden voor elke architectuur die je ondersteunt. Je wilt nog steeds een architectuur-onafhankelijke representatie en iets dat in staat is dit te vertalen naar de doelmachine. Met andere woorden, je hebt minstens een deel van een compiler en een hogere-niveau taal opnieuw uitgevonden, zelfs als die taal nooit bedoeld was om door mensen geschreven te worden.
Ten tweede hebben we geen enkele taal of computationeel model gevonden dat in alles uitblinkt. We hebben systeemprogrammeertalen, theorem provers, talen voor concurrente, gedistribueerde en veerkrachtige software (bijv. Erlang/Elixir), querytalen, hardware-beschrijvingstalen, enzovoort. Ze coderen verschillende semantieken en verschillende abstractieniveaus, met verschillende garanties. Het is onredelijk om te verwachten dat één enkele lagere-niveau taal dit alles kan verenigen.
Als programmeertalen dus niet verdwijnen, maar we stoppen met het optimaliseren ervan voor de mensen die ze schrijven, waar moeten we ze dan wél voor optimaliseren?
Agent-gestuurde tooling
In het verleden heb ik gezegd dat het creëren van geweldige tools voor mensen ook leidt tot geweldige tools voor agenten. Ik geloof dat dit altijd waar zal blijven. Dit heeft ertoe geleid dat we oplossingen bouwen die automatiseren wat we zelf al doen: agenten schrijven dezelfde tests als wij, consumeren dezelfde programmametadata en lezen dezelfde logs.
Maar wat als we agenten gaan gebruiken om acties uit te voeren die we normaal gesproken zelf niet zouden doen? Misschien omdat ze te tedious zijn, een steile leercurve hebben, of meer informatie vereisen dan praktisch is voor een mens? Dat is wat deze sectie onderzoekt.
De tools in deze sectie vereisen niet dat agenten het grootste deel van de code schrijven. Zelfs als je agenten slechts voor 20% van je code gebruikt, kunnen ze profiteren van de hier beschreven tools.
Sterkere garanties boven gebruikersbeperkingen
Programmeertalen balanceren verschillende concurrerende doelen, waaronder expressiviteit, garanties en ergonomie. We willen de programma's kunnen uitdrukken waar we om geven, we willen dat de taal nuttige eigenschappen over die programma's vaststelt, en we willen het toegankelijk maken voor ontwikkelaars. Als agenten het grootste deel van de code schrijven, kunnen we deze afwegingen heroverwegen.
Een voorbeeld hiervan is de inferentie van functiesignaturen. Dit is waardevol voor mensen omdat het tedious is om informatie expliciet op te schrijven die de compiler kan afleiden. Coding-agents geven niets om tedious werk. Sterker nog, het expliciet maken van types en intenties geeft de compiler, andere agenten en onszelf meer informatie om mee te werken. Belangrijker nog is dat talen waarvan de types volledig kunnen worden afgeleid, over het algemeen een subset zijn van talen waarvan de types kunnen worden gecontroleerd. Optimaliseren voor inferentie kan dus uiteindelijk zowel de expressiviteit als de garanties die een typesysteem kan bieden, beperken. Waarom zouden we die beperkingen opleggen aan agenten, terwijl we al hebben gezien dat ze in staat zijn om bewijzen te schrijven in veel complexere systemen?
Garanties hoeven bovendien niet altijd statisch vastgesteld te worden. Geheugenveiligheid kan statisch worden afgedwongen of door de runtime, zoals via garbage collection. Model checking kan modellen en implementaties overbruggen door model-gegenereerde executie-traces te gebruiken om het werkelijke systeem te valideren. Een voorbeeld: Erlang/Elixir vertrouwen op geïsoleerde processen en message passing om te beperken hoe concurrente programma's zijn gestructureerd, waarbij een deel van de expressiviteit wordt opgeofferd voor sterkere eigenschappen rond isolatie en fouttolerantie. Niet elk concurrent algoritme past efficiënt in dit model, maar programma's die dat wel doen, erven nuttige garanties.
Over het algemeen is er nog nooit een beter moment geweest om sterkere garanties te bieden over onze software. We zijn niet in staat om alle software formeel te verifiëren, maar we kunnen verschillende benaderingen combineren om deze te versterken:
- Correct by construction: de taal maakt ongeldige toestanden of programma's moeilijk of onmogelijk om uit te drukken.
- Statisch vastgesteld: types, bewijzen en statische analyse stellen eigenschappen vast vóór de executie.
- Runtime-afgedwongen: geheugenbeheer, isolatie, capability-grenzen en andere door de runtime afgedwongen eigenschappen.
- Empirisch gevalideerd: programmavalidatie via tests, property-based testing en fuzzing.
Ik geloof dat de verschillende manieren waarop talen deze technieken combineren en de grens tussen expressiviteit en garanties verleggen, een toenemende rol zullen spelen in hoe ze zich differentiëren en worden geadopteerd. Zeker als je gelooft dat agenten het voor ecosystemen gemakkelijker maken om elkaar in te halen, zoals besproken in "Over de community". In dezelfde lijn moeten frameworks deze praktijken ook adopteren op hun eigen abstractieniveau.
Programmadatabases boven LSPs
De dood van de IDE's is de afgelopen twee jaar meerdere keren uitgeroepen. Zodra de overlijdensadvertentie eindelijk is gepubliceerd, verwacht ik dat LSPs (Language Server Protocols) ook niet zullen overleven.
Het Language Server Protocol is primair ontworpen voor IDE's, en veel van de operaties zijn gericht op documenten en posities: bestand, regel en kolom, zaken die agenten niet precies bijhouden. Uit onze ervaring met het bouwen van Tidewave blijkt dat een CLI of tool waarin agenten kunnen vragen "waar is de documentatie voor foo_bar?" of "waar is BarBaz gedefinieerd?" veel geschikter is dan hen te vereisen een precieze referentie te geven naar waar die symbolen in de broncode verschijnen.
Bovendien zijn LSPs ontworpen om informatie te presenteren voor menselijke consumptie in plaats van exploratie. Je verzamelt informatie stukje bij beetje door tussen bronbestanden te springen.
Het goede nieuws is dat veel language servers al beschikken over veel van de informatie die coding-agents nodig hebben: symbolen, referenties, call-graphs, type-informatie en soms data-flow informatie. Mijn suggestie is om deze informatie bloot te stellen als een programmadatabase met een querytaal, of dat nu SQLite, Datalog of een custom DSL is.
Het zou onredelijk zijn om van de meeste ontwikkelaars te vragen een query te schrijven om alle referenties naar een functie te vinden. Coding-agents zouden dat echter graag doen: het schrijven van een programquery kost hen evenveel moeite als het aanroepen van een CLI- of LSP-tool. Belangrijker nog is dat ze queries kunnen samenstellen die onpraktisch zouden zijn om als individuele IDE-functies aan te bieden: zoek alle publieke functies die uiteindelijk deze functie aanroepen, of alle paden door het programma waar een bepaalde waarde nil kan worden. Deze databases zouden ook als linters gebruikt kunnen worden om agenten te beschermen tegen ongewenste praktijken.
Dit impliceert dat lokaliteit extreem belangrijk blijft, vooral in grote codebases. Functies zoals monkey-patching, impliciete hooks, dynamische rebinding en andere vormen van action at a distance betekenen dat code die op één plek is geschreven, kan beïnvloeden hoe het hele systeem zich gedraagt, op manieren die moeilijk te traceren zijn, zelfs wanneer programmadatabases beschikbaar zijn.
Runtime-waarneembaarheid boven debuggers
Debuggers zijn een andere interface die primair voor mensen is ontworpen. We zetten breakpoints, stappen regel voor regel door de executie en inspecteren variabelen terwijl we gaan. Agenten kunnen echter code instrumenteren, traces verzamelen en informatie veel sneller correleren dan wij. We moeten hen interfaces geven die daar gebruik van maken.
Bovendien, als de aanname is dat coding-agents het grootste deel van onze code gaan schrijven, is het redelijk om te verwachten dat zij ook meer verantwoordelijkheden op zich nemen in de software development lifecycle, inclusief het monitoren en diagnosticeren van productiesystemen terwijl ze draaien, in plaats van te vertrouwen op logs en dashboards.
We zouden de runtime en de status in onze systemen zo blootstellen dat agenten deze programmatisch kunnen bevragen en verkennen. Runtime-waarneembaarheid kan agenten een gemeenschappelijke interface geven om storingen te diagnosticeren, betrouwbaarheidsproblemen te identificeren en bottlenecks live in alle omgevingen te detecteren.
Gelukkig is dit een gebied waar Elixir altijd in heeft uitgeblonken, dankzij de Erlang VM. Het inspecteren van processen, sockets, applicaties, supervisors, ETS-tabellen, message queues en meer is een ingebouwde capaciteit van de runtime. De resterende stap is om deze capaciteiten veilig bloot te stellen aan agenten, of dat nu is via een verzameling tools, een querytaal of een sandbox.
***
Erkenningen: Ik wil Quinn Wilton, Chris McCord, Ryan Lopopolo, Chad Fowler, Rob Knight, Danila Poyarkov en de vele mensen die ik ontmoette op ElixirConf bedanken voor de discussies en het delen van werk dat heeft geholpen bij het vormen van de gedachten in dit artikel. Alle meningen zijn van mijzelf.
Disclaimer: AI is gebruikt om stilistische en grammaticale problemen in het artikel aan te pakken.
Groetjes,