Alle har ret til en fed semantik

Jeg sidder i øjeblikket og er ved at lægge sidste hånd på semantikken af de udtryk, som brugerne af vores webscraping-produkt skal bruge som bl.a. betingelser i tests af, om de data, man har udtrukket fra web'et ser fornuftige ud. Egentlig havde jeg lavet udtryks-frameworket for fire år siden, da jeg var studenterudvikler, men det har ligget og hygget sig i en skuffe indtil nu, hvor vi arbejder på en helt ny version af produktet, der bl.a. har fået nyt typesystem siden dengang.
 
Brugerskaren har også ændret sig lidt. Før var den typiske bruger en person, som havde erfaring med klassiske integrationsopgaver og var vant til at programmere i stærkt typede sprog som C og Java, men i dag har mange brugere oftere erfaring med sprog som JavaScript, hvor fortolkeren er meget venlig til at konvertere typer frem og tilbage. Det passer egentlig meget godt med webscraping-scenariet: Her har brugeren måske samlet noget tekst ind fra et website, testet og renset op på det, og har til sidst noget data liggende i en tekst-variabel, som egentlig kun indeholder tal, og han vil derfor gerne kunne lave aritmetik på det.
 
I JavaScript vil udtrykket 1 * "2.0" f.eks. evaluere til 2, mens det i Java vil give en kompileringsfejl. For den overloadede "+"-operator har man i JavaScript valgt ivrigt at konvertere til strenge, når en af operanderne er en streng, så både 1 + "1" og "1" + 1 giver strengen "11". I Java er 1 + "1" et ugyldigt udtryk, mens man har valgt at tillade "1" + 1, vel fordi det ellers ville være temmeligt irriterende at konstruere strenge, hvori der indgår tal. For JavaScripts vedkommende kan man sige, at det måske er lidt inkonsekvent (og dermed forvirrende for programmøren) at man kun ved visse operatorer forsøger at behandle strenge som tal.
 
Så hvor venlig skal man egentlig være til automatisk at konvertere typer for brugeren? Nogle gange er det måske bedre at få en fejl, end at komme til at arbejde videre på nogle forrykte data. Det er da sket mere end en gang, at jeg har set JavaScript, som kom til at lave streng-konkatenering i stedet for addition, og det er simpelthen så træls (selv for en sjællænder) at debugge.
 
Altså skal man holde tungen lige i munden, når man bestemmer sig for, hvilken semantik, ens udtryk skal have! Og det er klart, at brugerne vil have nogle forventninger baseret på hvilken slags sprog, de er vant til at arbejde med. Som man måske i øvrigt har bemærket, skelner JavaScript ikke internt mellem heltal og flydende tal, hvilket er endnu en ting at overveje. Især når man skriver literaler, har man ofte tendens til ikke at tænke over, om man skriver eksempelvis 3 eller 3.0 - med mindre, man har programmeret for længe ;-)
 
Semantikken i forbindelse med sammenligninger giver også anledning til en del overvejelser. Hvis man gerne vil lave automatisk typekonvertering, er det naturligt at lade 1 < "2" evaluere til true. Vi har valgt ikke at overloade "<"-operatoren til også at kunne anvendes til leksikografisk sammenligning af strenge, så "a" < "b" skal give en fejl. Men hvad nu med "1" < "2", er det så en ok sammenligning? I princippet kunne man jo konvertere til tal. Det har vi dog valgt ikke at gøre, fordi man så mister en meget hensigtsmæssig egenskab (som man har givet afkald på i JavaScript), nemlig altid statisk at kunne afgøre typen af et udtryk. Med statisk typecheckning bliver vi i stand til at lave en ret intensiv validering af udtryk; f.eks. checke, om de attributter, man forsøger at tilgå på en objekt faktisk er erklærede på objekter af den givne type, eller man kan advare brugeren, hvis han forsøger at tildele et udtryk, der evaluerer til en tekst, til en tal-variabel. Det er go' brugervenlighed, synes jeg.

Kommentarer (5)
sortSortér kommentarer
  • Ældste først
  • Nyeste først
  • Bedste først
Mikkel Meyer Andersen

Dynamisk typede sprog er lidt noget gris, omend de kan være meget rare og nemme at kode i - når det går godt. Som du nævner en enkelt gang, kan der dog fremkomme nogle (knapt så) "sjove" fejl. Derfor foretrækker jeg (som regel) statisk typede sprog, da det kan fange mange fejl, plus koden bliver nemmere at forstå efterfølgende; for hvor meget logik er der i at lave en plusoperation på to variabler af åbenlys forskellig type, bare fordi man ved, det går godt?

Nu du nævner typetjek, så kan det godt blive lidt en kompliceret affære - og især hvis der er en god portion af nedarvning og dynimisk dispatch.

Og slutteligt et par spørgsmål:
- Du nævner slet ikke typeinferens? Er det ikke noget, I har overvejet?
- Hvilken type semantik laver du? Forhåbentlig en formel én? Og ditto med typesystemet?

  • 0
  • 0
Anne-Sofie Nielsen

Hej Mikkel,

Nej, vi har ikke overvejet typeinferens - jeg synes ikke, at det er oplagt til den use case, vi har. Vores produkt laver webscraping ved hjælp af, hvad man kunne kalde et visuelt programmeringssprog, dvs. at brugeren indsætter en række steps (lidt svarende til et flow-diagram) over de handlinger, der skal foretages, f.eks. skal der loades en side og så klikkes på et element, itereres over rækker i en tabel og udtrækkes data fra udpegede elementer.

Brugeren trækker altså data fra websider ud i nogle typede variable, som han selv har erklæret. Her vil det f.eks. være rart, hvis han umiddelbart få en fejl, hvis han tror, at han skal udtrække noget, som er en dato, men viser sig at være noget helt andet. Men jeg har lidt svært ved at se, hvordan man meningsfuld skulle kunne lave typeinferens her.

Der er nedarvning mellem visse typer af objekter, men da objekterne ikke har metoder, eliminerer det en del mulige problemer ifht. f.eks. dynamisk dispatch, som du nævner.

Jeg havde egentlig ikke overvejet, om jeg skulle kaste mig ud i at lave en formel semantik; det burde være rimeligt overkommeligt med et begrænset typesystem og udtryks-framework. Omvendt er det jo også altid overvejelsen, om det er tiden værd - først og fremmest tjener det jo til at overbevise en selv om, at man har været "hele vejen rundt" i sine overvejelser. Men ultimativt skal resultatet jo alligevel være givet i implementeringen, og dokumenteres på en langt mere uformel måde til brugerne, som ikke kan forventes at kunne forstå en formel specifikation. Men det var en god pointe, som jeg bestemt vil overveje.

  • 0
  • 0
Mikkel Meyer Andersen

Hej,

Tak for den lange kommentar - det var dog rart :-)

Mht. typeinferens har du ret. Når det er som du beskriver, ville det måske lige frem være en forkert beslutning. Det er nok bedre at brugeren dikterer den forventede type, og så er det med at lave en god parsning således uoverensstemmelser vil blive fanget. Men når det på den måde drejer sig om data, er det stadig LL(k)-parser så vidt jeg lige kan gennemskue, så det burde ikke volde de store problemer at melde om typefejl på den led.

For nu at indlede lidt flueporno, kan man så ikke være fræk at påstå, at det ikke er objekter, hvis de ingen metoder har?

Tja, som sådan har udformningen af en formel semantik ikke noget med dit typesystem (før du vil til at lege med et bevis for at typerne er bevaret under din semantik). Jeg vil heller påstå, at kompleksiteten af semantikken afspejles af syntaksen af dit sprog. Og udover at en formel semantik sikkert ville få dig til at opdage fejl og klargøre de mørkere sider af dit sprog, så er det dejligt nemt at implementere sproget, når det har en formel semantik :-) Men jeg er nu helt enig med dig, at det givetvis ikke er værd at lave en komplet formel semantik, men for de mere komplekse dele, kunne det muligvis være behjælpeligt - også som dokumentation.

Men det lyder nu som et rigtigt spændende projekt, du har gang i, må jeg sige.

  • 0
  • 0
Torben Mogensen Blogger

Efter min mening bør man kun lave implicit konvertering, hvis man på oversættelsestidspunktet (f.eks. ved typecheck) kan garantere, at konverteringen går godt. Moralen er, at hvis noget kan give en køretidsfejl (eller en meningsløs værdi), så bør det være noget programmøren selv skriver og ikke noget oversætteren indsætter af misforstået hjælpsomhed for at undgå en typefejl.

Det betyder, at jeg ikke ville lave implicit konvertering fra tekst til tal, med mindre jeg kunne være sikker på, at teksten altid indeholdt et lovligt tal (og det kan man sjældent). Derimod ville jeg ikke have så meget imod implicit konvertering den anden vej, dvs. fra tal til tekst. På samme måde er automatisk konvertering fra heltal til kommatal O.K., men jeg foretrækker, at konvertering den modsatte vej er eksplicit (med forskellige operatorer for afrunding og afskæring).

Men udover, hvor der er flere rimelige måder, man kan konvertere, så kan man sagtens lave en enkelt overloaded konverteringsoperator, der konverterer fra argumentets type til den type, som konteksten forventer. Så kan oversætteren komme med en fejlmeddelelse, hvis der ikke eksisterer en konvertering, eller hvis det ikke er entydigt, hvilken type, konteksten forventer (f.eks. hvis konteksten selv er en overloaded operator).

Programmøren kan så evt. hjælpe oversætteren ved at sætte en typeannotering ind.

En konkret syntaks (som læner sig op ad C, Java osv.) kunne være, at () er en konvertering, hvor oversætteren finder måltypen ud fra konteksten, mens (type) er konvertering til den angivne type.

Så man kunne f.eks. skrive 2()"4" og få 8, mens 2(float)"4" ville få den overloadede * til at resolvere til floating point multiplikation, så der indsættes en implicit konvertering af 2 til 2.0, så resultatet her bliver 8.0.

  • 0
  • 0
Anne-Sofie Nielsen

Mikkel, du har principielt ret i, at det ikke er objekter, når nu de ikke har metoder. I vores tidligere version af produktet hedder de da også "modeller", hvilket er mere teknisk korrekt - men efter at have bemærket, at brugeren alligevel har tendens til at omtale dem som "objekter", er den betegnelse nu valgt fremover.

Man skal jo (desværre) ikke opdrage sine brugere...

Torben, i den tidlige version af udtryks-frameworket (den, der har ligget i skuffen siden jeg var ung og idealistisk studerende) havde faktisk casts og var ikke så venlig til at lave automatisk typekonvertering. Men jeg er bange for, at en ikke-teknisk bruger vil stå helt af på at blive tvunget til at indsætte casts. Hvis konsekvensen af et mislykket konverteringsforsøg alligevel er, at der opstår en fejl, har vi så ikke bare skudt ansvaret fra os ved at tvinge brugeren til at indsætte et cast? "Jamen han har jo selv bedt om det". Det bliver ikke nødvendigvis mere brugervenligt af, at brugeren får mere kontrol.

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