Jesper sandal header

Reverse Engineering af en programmørvittighed

Jeg undskylder på forhånd for den gamle vittighed, som jeg hørte fortalt igen for nylig. Denne gang var der ét eller andet, som did not compute. Og heldigvis bliver vittigheden meget mere interessant, når vi begynder at se på, hvor dårlig den i virkeligheden er:

En programmør bliver bedt af sin kone om at gå ned i supermarkedet og købe 2 liter mælk. På vej ud af døren råber hun: "Hvis de har æg, så køb 12!"

Da han vender tilbage, har han favnen fuld af de 12 liter mælk, han har købt. Da konen spørger, hvorfor han har købt al den mælk, svarer han: "De havde æg."

Denne vittighed har cirkuleret længe, og på overfladen er den jo også meget finurlig, selvom den ikke er en lårklasker. Den har også været på Reddit, hvor brugerne forsøgte at kode sig til en forklaring. Reddit-brugerne overser imidlertid en meget væsentlig detalje, nemlig at ovenstående scenarie aldrig ville have udspillet sig, hvis der var tale om en rigtig programmør.

(Jeg gætter på, at vi ikke skal tre kommentarer ned under dette indlæg, før der er en supernørd, der påpeger noget i stil med, at der findes en obskur variant af LISP, som ville fortolke det helt ligesom i vittigheden)

Lad os se på noget kode!

KØB(2 liter mælk)
HVIS (ÆG) SÅ { KØB(12) }

Sådan ser kernen i konens instruks ud. Allerede her kan vi se, at der er noget galt. Vi ved godt nok ikke helt, hvordan KØB-funktionen er implementeret, men det er vanskeligt at forestille sig en implementering, hvor den blot ville acceptere et heltal som argument.

Altså ville programmøren returnere "invalid or missing argument" til sin kone (og nu vil onkelhumor-segmentet udbryde: "Og det skal man aldrig gøre!").

Umiddelbart ville en overlæsning af funktionen KØB nok ligne noget i stil med:

function KØB(antal, vare) { }
function KØB(vare) { }

I så fald ville KØB(12) formentligt blive fortolket som en streng "12", hvor man med lidt god vilje kunne antage, at funktionen matcher med varenavne i butikken. Hvis det var tilfældet her, ville vi forvente, at programmøren returnerede med varen "12".

Muligvis kan vi nøjes med at matche strengen med begyndelsen af et varenavn, og så ville programmøren returnere med den første vare, der begyndte med "12". Det ville næppe være "12 æg", hvis varerne i butikken er sorteret alfabetisk. Måske snarere "12 Albani Classic Pilsner".

Lad os se på, hvad der kunne være gået galt i programmørens implementering af instruktionen fra konen. Vi udvider lidt med min kludetæppekode:

var vare = "1 liter mælk";
var antal = 2;
KØB(antal, vare);
if (æg) { KØB(12) }

Ud fra denne kode er det vanskeligt at se, hvordan programmøren endte med 12 liter mælk. Men han kunne måske være endt med 14 liter, hvis "vare" var en global variabel, og en overlæsset KØB-funktion, der kun modtog et heltal, brugte den globale vare-variabel, som var blevet sat til mælk. Nu er vi ude i noget meget dårlig kode.

Lad os prøve at reverse engineere koden ud fra instrukserne og resultatet:

var vare = "1 liter mælk";
var antal = 2;
if (æg) {
var antal = 12;
}
while (i <= antal) {
KØB(vare);
}

… er umiddelbart den mest enkle fungerende implementering, jeg kan finde frem til. Men det forudsætter, at vores programmør går lidt videre end instruktionerne fra konen, og dels antager, at "12" er et antal, og dels sjusker ved at erklære den samme antalsvariabel med en ny værdi, og så begynder det at ligne en vittighed om en programmeringsfejl, og det er ikke noget, man skal spøge med.

Samtidig ændrer vi lidt på kontinuiteten ved faktisk først at forsøge at købe de første to liter mælk, efter vi har vurderet værdien af udsagnet "æg". Vi kan komme lidt tættere på intentionen i vittigheden ved at bytte rundt og implementere indkøbsturen objektorienteret således:

var vare = new Vare(mælk);
vare.køb(2);
if (ÆG) {
vare.køb(12);
}

Eller:

var vare = new Vare(mælk);
if (ÆG) {
vare.køb(12);
}
else {
vare.køb(2);
}

Vi er altså ude i nogle helt specielle forudsætninger for koden bag en supermarkedstur for at opnå det samme resultat, hvor programmøren fortolker kravspecifikationen ud over det udtalte.

Paradokset er derfor, at netop dén type programmør, som vittigheden handler om, aldrig ville begå fejlen, men derimod returnere en fejlmeddelelse til sin kone.

Skarpsindige læsere vil nok fra begyndelsen have indvendt, at hele øvelsen er forgæves. Dét, der i virkeligheden ville være sket var, at programmøren forsvandt i nogle timer, hvorefter han var vendt tomhændet tilbage efter lukketid.

Alt sammen på grund af den allerførste instruks fra konen:

GOTO SUPERMARKEDET

Kommentarer (36)
sortSortér kommentarer
  • Ældste først
  • Nyeste først
  • Bedste først
Povl H. Pedersen

må han da have gjort det helt rigtige.
Det eneste der sættes i en købe-kontekst er mælk.
Æg er alene en betingelse.

Der er 2 grunde til at man kan gætte at det er æg hun mener:

  1. Der er ingen grund til at blande æg ind i det hvis de kun skal have mælk, medmindre konen er den type der selv vil købe æg, for at sikre sig hun får den bedste af de bakker de nu har (ikke usandsynligt)
  2. Æg sælges ofte i 12 stk pakker.
  • 4
  • 0
Tommy Ravn Jensen

...Resultatet afhænger kun af indholdet af $_, som de to tilsyneladende ikke er enige om... (;-)

Når vi nu er i den nørdede afdeling - som funktionelt program returnerer "Køb(12)" en funktion, der tager en vare som argument. En rigtig (LAZY) programmør vil derfor stadig stå i supermarkedet og vente på, at konen fortæller ham, hvilken vare, det er, der skal hentes 12 ned af fra hylderne...

...Det afhænger helt af øjnene som ser (;-)

  • 3
  • 0
Tine Andersen

Som indehaver af en nørd har jeg lært, ikke at stille to spørgsmål i et, som fx: Vil du have kaffe eller te? Svaret er: Ja!
Og min nørd (der kan huske telefonnumre, som han kun har hørt én gang), kan ikke huske mere end max. tre varer. Derfor få han
altid en huskeliste med.

Forresten hedder øllen Odense Classic, men det er ganske rigtigt Albani, der producerer den. (flue-xes)

Mvh
Tine-

Men vittigheden var da sjov!

  • 7
  • 0
Jesper Stein Sandal

Den har jeg også overvejet at pille fra hinanden, men så er vi mere ovre i matematik (hovedkonklusionen ville være, at udsagnet er sandt for n >= 2, og talsystemet derfor er underordnet for talsystemer baseret på heltal >1 (tilgiv mig, hvis jeg udtrykker mig upræcist - det er alt for mange år siden, jeg har brugt min abstrakte algebra)). Og matematikere er der ikke noget sjov ved.

  • 0
  • 0
Peter Warholm

Jeg har været ude for at en der var sendt til grossisen for at købe 5 x 1/2 pint Piske fløde (skrevet i hånd "Wipping cream: 5 ½pint" i en list med den format vare: antal [mellemrum] størrelse) vendt tilbage med 5½ pint (et dunk + ½ pint beholder)...

  • 0
  • 0
Finn Aarup Nielsen
class Buying():  
    def __init__(self):  
        self.last_good = None  
    def __call__(self, number, good=None):  
        if good is None:   
            good = self.last_good  
        else:  
            self.last_good = good  
        return {good: number}  
   
   
basket = dict()  
buy = Buying()  
store = ['milk', 'eggs']  
   
basket.update(buy(2, 'milk'))  
if 'eggs' in store:  
    basket.update(buy(12))

Her kommer manden hjem med 12 liter mælk.

  • 2
  • 0
Kai Birger Nielsen

Hvad nu, hvis hun havde sagt: "Og hvis de har tilbud, så køb 12!"
Desuden er det jo formuleret, som om det er konen, der er på vej ud ad døren, så hele historien tyder på at fortælleren ikke har styr på detaljerne. Det har sikkert heller ikke været mælk, han havde med hjem :-)

Der kan jo heller ikke være noget problem. Minimumskravet var 2 liter mælk og der var ikke nogen yderligere instrukser, hvis supermarkedet ikke havde haft æg, så konen havde ikke specielt brug for æg.

  • 0
  • 0
Palle Simonsen

der påpeger noget i stil med, at der findes en obskur variant af LISP, som ville fortolke det helt ligesom i vittigheden)

Den obskure Lisp variant er denne gang CLIPS se f.eks. https://en.wikipedia.org/wiki/CLIPS en implementering af RETE algoritmen der, som alle jo ved, var grundlag for bl.a. OPS5 og XCON/R1 samt nogle moderne Business Rules systemer.

Nå, men til sagen.

Følgende CLIPS program modellerer vores programmørs kones ordre og ræsonnement. Første regel 'milk' modellerer ordren med at købe mælk medens der er givet to varianter af at købe æg. Først programmørens variant og dernæst den mere kedelige udgave; konens variant. Til sidst er der defineret nogle fakta: At ordren går ud på at købe mælk samt, at forretningen har æg.

(defrule milk "Buy milk"  
    (order milk)  
    =>  
    (assert (buy 2 milk)))  
   
(defrule eggs-1 "Buy eggs or ..."  
    (has eggs)  
    (order ?x)  
    =>  
    (assert (buy 12 ?x)))  
   
(defrule eggs-2 "Buy eggs or ..."  
    (has ?x)  
    =>  
    (assert (buy 12 ?x)))  
   
(deffacts joke-facts  
    (has eggs)  
    (order milk))

Hvis man afvikler programmet sker følgende:

CLIPS> Loading Selection...  
Defining defrule: milk +j+j  
Defining defrule: eggs-1 +j+j+j  
Defining defrule: eggs-2 +j+j  
Defining deffacts: joke-facts  
CLIPS> (reset)  
<== f-0     (initial-fact)  
==> f-0     (initial-fact)  
==> f-1     (has eggs)  
==> f-2     (order milk)  
CLIPS> (agenda)  
0      milk: f-2  
0      eggs-1: f-1,f-2  
0      eggs-2: f-1  
For a total of 3 activations.  
CLIPS> (run)  
==> f-3     (buy 2 milk)  
==> f-4     (buy 12 milk)  
==> f-5     (buy 12 eggs)  
CLIPS> 

For lige at repeterer i forsimplet form, skulle man have glemt RETE eksekvering:

(reset) indlæser vores fakta
(agenda) viser RETE algoritmens execution plan
(run) afvikler programmet

Som anført ovenfor i tråden køber programmøren selvfølgelig 14 liter mælk, medens den mere kedelige variant resulterer i 2 liter mælk og 12 æg.

CLIPS er let at finde på nettet og de præbyggede binaries kan afvikles i OSX eller Windows. Så udfordringen er givet videre. Hvem kan lave en endnu bedre model af vitsen?

Go' fredag aften :)

  • 4
  • 0
Jens Henrik Sandell

Hvis hvert bogstav i denne artikel var lig 1 milliliter vand. Hvor meget har I så til sammen hældt ud af ørerne, nu?

@Jesper
Fantastisk valg af emne! Det er vist heldigt at nyhedsmedierne ikke skal klarere CO2 udledning på en god omgang fredagshumor/gas.

PS min kone forstår den heller ikke.

  • 0
  • 0
Michael Olesen

Paradokset er derfor, at netop dén type programmør, som vittigheden handler om, aldrig ville begå fejlen, men derimod returnere en fejlmeddelelse til sin kone.

Tilføj en anden slags intelligens (fx forretningsforståelse eller erfaring) og svaret bliver straks mere komplekst: "Du skal ikke gøre det som chefen... ehh... konen siger, men det som hun tænker" :)

Gad vide hvordan det vil se ud i kode, hvis programmøren hverken må få en UnhandledBrainException eller spørge ind til en tvetydig kommando, men i stedet skal parse input i forhold til tidligere eksekveringer?

  • 1
  • 0
Nils Bøjden

En programmør ville være taget afsted, i butikken været faldet over de logiske fejl i input og derefter være vendt hjem med en 2 stk 6-pack Sort Guld (en i hver hånd) så turen ikke havde været forgæves og tiden havde været udnyttet til noget fornuftigt.

  • 2
  • 0
Michael Zedeler

KØB "mælk", 2 ELSE IF EXISTS("æg") THEN 12

Hvor 2 ELSE IF EXISTS("æg") THEN 12 parses som et udtryk. Den slags findes i sprog som Coffescript, hvor man kan sådan noget her:

a = if b then 1 else 2

Ellers kan det måske også forklares med et stak-baseret sprog:
"mælk"
2
KØB
"æg"
HVIS-SÅ
BEGYND
POP
POP
PUSH
12
PUSH KØB
SLUT

Det er stadig en kende søgt.

  • 0
  • 0
Jens Hilligsøe

Jeg er lidt overrasket over at ingen har påpeget det indtil nu, men du får hverken initialiseret "i" i dit reverse engineerede eksempel eller talt den op efter hvert køb - så han må enten komme fra butikken med alt deres mælk eller ingenting, for i <= antal bliver i hvert fald ikke ændret.
(Med mindre KØB gør det, men det vil ændre på de scoping regeler som jeg lige opfanger ud af din kode der)

  • 0
  • 0
Log ind eller Opret konto for at kommentere