You may implement this later: Cofunctors as partial implementations
Dit artikel stelt voor om cofunctoren (of retrofunctoren) te interpreteren als partiële implementaties die specifieke backend-keuzes, zoals datarepresentaties en algoritmen, uitstellen tot runtime op basis van de systeemtoestand.
Oorspronkelijk artikel gelicentieerd onder CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/). Dit is een AI-gegenereerde uitleg van het onderstaande artikel. Het is niet geschreven of goedgekeurd door de auteurs. Raadpleeg het oorspronkelijke artikel voor technische nauwkeurigheid. Lees de volledige disclaimer
In de wereld van software engineering voelt het bouwen van een systeem vaak als het assembleren van een complexe machine waarbij elke tandwiel gekozen moet worden voordat de eerste bout wordt aangedraaid. Ingenieurs worden vaak geconfronteerd met een dilemma: ze moeten de algemene structuur van een programma ontwerpen, zoals een database of een netwerkdienst, maar kunnen nog niet beslissen over de specifieke details, zoals welke opslagmotor te gebruiken of hoe de datareplicatie moet worden afgehandeld. Traditionele methoden voor het omgaan met deze onzekerheid houden meestal in dat er aan het begin een enkele set keuzes voor het hele systeem wordt vastgelegd, of dat men wacht tot het allerlaatste moment om de gaten op te vullen. Dit creënd een rigide proces waarbij het pad vooruit lang voordat de bestemming volledig duidelijk is, al is vastgelegd. De uitdaging ligt in het vinden van een manier om systemen te bouwen die kunnen evolueren, waarbij beslissingen die vroeg worden genomen op natuurlijke wijze de opties voor later vormgeven, zonder de programmeur te dwingen voortijdig tot een definitieve oplossing te komen.
Een onderzoeker aan de Universiteit van Oxford heeft een nieuwe manier voorgesteld om dit probleem te benaderen, gebruikmakend van een wiskundig concept genaamd een cofunctor om te beschrijven hoe software in stadia kan worden gebouwd. De kern van het idee is om een softwaresysteem niet te behandelen als een afgewerkt product, maar als een verzameling verplichtingen en keuzes die veranderen naarmate het systeem groeit. Stel je een blauwdruk van een huis voor die begint met een basisomtrek. Naarmate de architect een nieuwe kamer toevoegt, wordt de blauwdruk niet alleen groter; de lijst met benodigde materialen wordt ook bijgewerkt. Als de architect besluit een tweede verdieping toe te voegen, vereist de blauwdruk mogelijk nu een sterkere fundering, een keuze die niet relevant was toen het huis slechts één verdieping had. Deze nieuwe aanpak stelt ingenieurs in staat om deze evoluerende lijsten met vereisten door het ontwerpproces te dragen, waardoor wordt gewaarborgd dat elke nieuwe beslissing compatibel is met de beslissingen die eraan voorafgingen, terwijl de definitieve details voor later open blijven.
Het artikel betoogt dat bestaande instrumenten voor het beheren van softwareconfiguraties vaak te rigide zijn. Ze vereisen doorgaans een globale set parameters die aan het begin gedefinieerd moeten worden, wat betekent dat het systeem niet gemakkelijk kan adapteren als er halverwege de ontwikkeling een nieuwe vereiste naar voren komt. Bijvoorbeeld, het kiezen om gegevens op een specifieke manier op te slaan, kan later een beslissing afdwingen over hoe die gegevens over verschillende servers te repliceren, maar standaardmethoden worstelen ermee om deze twee beslissingen dynamisch aan elkaar te koppelen. De auteur suggereert dat door een softwaresysteem te beschouwen als een "partiële implementatie", waarbij de huidige staat van het systeem bepaalt welke keuzes vervolgens beschikbaar zijn, we een flexibeler engineeringproces kunnen creëren. Dit gaat niet alleen over het uitstellen van beslissingen; het gaat over het structureren van het systeem zodat de handeling van het nemen van één beslissing op natuurlijke wijze het menu aan opties voor de volgende update.
Om dit te demonstreren, gebruikt de auteur het voorbeeld van een gegevensopslagsysteem. Aanvankelijk kan het systeem simpelweg gedefinieerd zijn als een plek om gegevens te bewaren. Op dat stadium heeft de ingenieur nog niet besloten of er een lokale database, een remote service of een specifiek bestandsformaat gebruikt zal worden. Naarmate het ontwerp vordert, kan de ingenieur een vereiste toevoegen dat de gegevens persistent moeten zijn, wat betekent dat ze bestand moeten zijn tegen stroomuitval. Deze nieuwe vereiste werkt de staat van het systeem bij, wat een nieuwe reeks keuzes met betrekking tot duurzaamheid introduceert. Later, als de ingenieur besluit de gegevens naar meerdere locaties te repliceren voor de veiligheid, wordt het systeem opnieuw bijgewerkt. Deze tweede wijziging kan een vereiste introduceren voor een transactieprotocol, een detail dat niet bestond toen het systeem slechts een eenvoudige opslagplaats was. De schoonheid van deze aanpak is dat het systeem automatisch op conflicten controleert. Als de ingenieur een eenvoudig bestandsformaat had gekozen dat geen transacties kan verwerken, zou het systeem dit direct als een conflict markeren wanneer de replicatievereiste wordt toegevoegd, in plaats van te wachten tot de code is geschreven en het later misgaat.
De onderzoeker laat zien dat deze methode de creatie van uitvoerbare migratieplannen mogelijk maakt. In plaats van alleen een lijst met vereisten op te schrijven, kan het systeem een stapsgewijs plan genereren voor hoe een basisopslag kan worden getransformeerd naar een complexe, gerepliceerde opslag. Dit plan kan in stadia worden opgebouwd, waarbij elke stap wordt geverifieerd tegen de huidige staat van het systeem. Als een stap wordt overgeslagen of buiten de juiste volgorde wordt uitgevoerd, kan het systeem de fout detecteren. Bijvoorbeeld, een plan dat probeert gegevens te repliceren voordat er een duurzame opslag is aangemaakt, zou worden afgewezen omdat de noodzakelijke fundering nog niet bestaat. Dit zorgt ervoor dat het uiteindelijke systeem op een solide logisch pad wordt gebouwd, waarbij elke verandering consistent is met de geschiedenis van eerdere veranderingen.
Een van de belangrijkste bevindingen is dat deze aanpak niet vereist dat de ingenieur vooraf elke mogelijke toekomstige staat van het systeem opsomt. In veel traditionele methoden moet men alle mogelijke configuraties vooraf definiëren, wat overweldigend kan zijn en vaak leidt tot een combinatorische explosie van opties. Hier houdt het systeem alleen de verplichtingen bij die momenteel actief zijn. Naarmate er nieuwe vereisten worden toegevoegd, verschijnen er nieuwe keuzes, en naarmate oude vereisten worden voldaan, verdwijnen ze. Dit houdt de complexiteit beheersbaar. De auteur merkt op dat hoewel het wiskundige kader achter dit idee geavanceerd is, de praktische toepassing ervan rechttoe eigenlijk is: het biedt simpelweg een manier om de stroom van beslissingen te beheren op een manier die de afhankelijkheden tussen hen respecteert.
Het artikel behandelt ook waarom dit idee voorheen niet breed werd toegepast in de programmering. De term "cofunctor" is historisch gezien verward met andere concepten, wat leidde tot een gebrek aan duidelijkheid over de specifieke bruikbaarheid ervan. Bovendien richtten eerdere pogingen om soortgelijke problemen op te lossen, zoals die met betrekking tot database-updates of modulaire programmering, zich vaak op andere aspecten, zoals het consistent houden van gegevens of het samenvoegen van codemodules, in plaats van op de dynamische evolutie van implementatiekeuzes. De auteur suggereert dat door cofunctoren te herformuleren als een hulpmiddel voor partiële implementatie, het concept veel toegankelijker en direct toepasbaar wordt op het dagelijkse werk van software engineers.
Uiteindelijk biedt het werk een nieuw perspectief op hoe we complexe systemen bouwen. Het suggereert dat de beste manier om met onzekerheid om te gaan niet is om het ontwerp vast te leggen of het volledig open te laten, maar om een structuur te creëren waarin het ontwerp natuurlijk evolueert. Door software te behandelen als een levend document van keuzes en verplichtingen, kunnen ingenieurs systemen bouwen die robuust, aanpasbaar en gemakkelijker te begrijpen zijn. Het resultaat is een methode die het mogelijk maakt om complexe systemen te assembleren terwijl de definitieve, concrete details worden uitgesteld tot het moment dat ze werkelijk nodig zijn, wat garandeert dat het gevolgde pad altijd logisch en consistent is.
Verdrinkt u in papers in uw vakgebied?
Ontvang dagelijkse digests van de nieuwste papers die bij uw onderzoekswoorden passen — met technische samenvattingen, in uw taal.