Torben Mogensen header

Det første sprog (del 4)

De sidste par afsnit har primært handlet om grænseflader og ikke så meget om sproget selv. Det vil jeg råde bod på nu.

Som nævnt, er de vigtigste designkriterier at bygge på brugernes tidligere erfaring og at holde begreber og sprogkonstruktioner så vid muligt i en 1:1 relation. Disse to kriterier kan dog komme i konflikt med hinanden: De fleste i målgruppen er vant til at se funktionsdefinitioner i stil med

  • f(x) = 2x+1*

så den slags definitioner vil jeg gerne bruge. Men en sådan definition bruger flere begreber samtidigt:

  1. Navngivning af en funktion
  2. At navnet *x* er lokalt i definitionen og kan få forskellige værdier i forskellige kald til funktionen.

Men jeg synes, at både det velkendte ved funktionsdefinitioner og det, at stort set alle sprog har lignende definitioner, opvejer sammenblandingen af de to begreber. Dog vil jeg gerne kunne introducere navngivning uafhængigt af (og før) funktionsdefinitioner, så jeg vil også have definitioner i stil med

x = 27

Her er det værd at bemærke, at denne konstruktion er en definition af et nyt navn og ikke en tildeling af en ny værdi til et (måske) eksisterende navn, sådan som det ville være i C eller BASIC. Jeg vil også tillade tildelinger, men dem venter vi lidt endnu med.

Men hvad sker der så, hvis man definerer samme navn to gange, f.eks.

x = 27
x = 42

Her er to muligheder: Man får en fejlmeddelelse, eller den seneste definition er den gyldige. Jeg vil foretrække en fejlmeddelelse, da en dobbeltdefinition ofte er tegn på en programmeringsfejl, kan være forvirrende, og ret nemt undgås ved at bruge et nyt navn i stedet.

Med variabeldefinitioner, funktionsdefinitioner og funktionskald (som jeg ikke har vist, men som ser ud som I forventer) har vi næsten et simpelt funktionsprogrammeringssprog. Men vi skal bruge en ting mere: Betingelser.

Den mest oplagte løsning ville være at bruge den klassiske if-then-else konstruktion, men det er ikke klart, at det er den bedste løsning. For det første er den sprogcentreret. Jeg tvivler ikke på, at enhver teenager i Danmark ved, hvad ordene betyder, men det er netop det, der kan være et problem: Man kan forledes til at formulere sig på en måde, der er mere engelsk end programmering, f.eks.

if y<3 then x=15 else x=13

som pendent til den engelske sætning "if y is less than 3, x is 15, otherwise x is 13", som også viser, at ordet "else" ikke er velvalgt. I de fleste programmeringssprog vil dette være en ulovlig dobbeltdefinition (eller lovlige tildelinger, men det er dfinitioner vi vil have). I de fleste funktionssprog skulle ovenstående defineres ved

x = if y<3 then 15 else 13

men den ligner ikke engelsk ret meget, og her er brugen af engelske ord mere forvirrende end gavnlig. Selvfølgelig kan man vænne sig til det (det har de fleste af os gjort), men det kan måske gøres bedre.

Desværre kan vi ikke hente noget fra målgruppens erfaringer. Der er ganske vist en matematisk notation for betingede definitioner, men hverken denne notation eller betingede definitioner er mig bekendt pensum i gymnasiet, uanset matematikniveau.

Det nærmeste til betingelser, som min datter har haft i skole og gymnasium er implikation: "Medfører"-tegnet. På grund af manglende specieltegn i BBCode, vil jeg skrive det som "=>", men det bør retteligt være et enkelt tegn. En del af den betingede definition herover kunne skrives som

y<3 => x=15

men der er ikke nogen "else" del til et medførertegn, så hvis man vil bruge målgruppens erfaringer bliver man nødt til at skrive

(y<3 => x=15) ^ (¬y<3 => x=13)

men det er ikke specielt læseligt, og det begynder at ligne avanceret logik mere end et simpelt programmeringssprog, og vil give en forventning om, at sproget vil forstå vilkårlige logiske udsagn. Det er der også sprog, der kan (constraint logic languages), men de er efter min mening ikke egnede til at lære folk at programmere, da eksekveringsmodellen er meget kompliceret.

Nogle funktionsprogrammeringssprog bruger vogtede ligninger (guarded equations) i stedet for betingede udtryk eller betingede definitioner af variabler. F.eks. kan funktionen for absolut værdi defineres som

abs(x) | x<0 = -x
abs(x) | x>=0 = x

hvor den lodrette streg kan læses som "når" eller "hvor". Men jeg synes, at denne konstruktion blander betingelser og funktionsdefinitioner sammen, hvor jeg helst så betingelser som et adskilt begreb. Helt adskilt bliver lidt svært: Der skal være noget, der gøres betinget: En beregning, en definition eller en effekt.

En mulighed er, at alle disse begreber i sproget kan gøres betingede: Den samme betingede konstruktion bruges til at gøre udtryk, definitioner eller effekter betingede. Men betingede definitioner giver et problem: Hvis et navn kun er defineret, når betingelsen er sand, hvad betyder navnet så, hvis betingelsen er falsk? En mulighed er, at kræve at en betingelse altid har en gren til "alle andre tilfælde", og at alle grene skal definere de samme samme navne. Men det kan godt være en tand for kompliceret til begyndere. Så er det måske bedre at begrænse betingelser til beregninger og/eller effekter.

Da udtryk altid skal have en værdi, bliver man nødt til at lade dm være defineret uanset betingelsens udfald, så man skal have noget, der svarer til en else-gren. Det samme gælder ikke effekter: Man kan sagtens have en effekt, der kun sker i bestemte situationer, og ellers sker der bare ingenting. Det er grunden til, at imperative sprog ofte har betingede sætninger uden else-del (de fleste gamle BASIC-sprog havde faktisk kun denne form), mens funktionssprog i reglen kræver en else-del (eller tilsvarende). Men at have to forskellige konstruktioner går imod ideen om en konstruktion for hvert begreb, så i stedet vil jeg antage, at der er en "ingen effekt" konstruktion, så man eksplicit skal sige, hvis der ellers ikke sker noget.

Derfor bliver valget en konstruktion, der er ækvivalent til den funktionelle if c then a else b *(som i C findes som *c'a:b). Som sagt er jeg lidt imod brugen af de engelske ord if, then *og specielt *else, men jeg synes ikke, at C's alternativ er bedre. Jeg foretrækker, at man med det samme kan se, at der er tale om en betingelse, så at have en (måske lang og kompliceret) betingelse inden symbolet, der viser, at der er tale om en forgrening, er nok ikke heldigt. Det kan også være en god ide, at vise afslutningen på det betingede, altså med noget svarende til endif. Man kunne tage inspiration fra spansk, hvor et spørgsmål i skrift starter med ¿ og slutter med '. Men det er måske lidt for "smart", og igen vil brugen af et kendt symbol (her ?) i en anden betydning end sædvanligt være forvirrende. De fleste mennesker bruger ikke firkantede parenteser til noget i almindeligt sprog, så en mulighed kunne være, at lade en betinget struktur starte med [ og slutte med ]. En konkret syntaks kunne være [a -> b | c] som ækvivalens til if a then b else c. Som eksempel vil funktionen for absolut værdi kunne skrives som

abs(x) = *[0>x-> -x | x*]

Dette eksempel viser en af farerne ved at bruge - og > til at lave en pil: De to brug af > ligner hinanden, men betyder noget helt forskelligt. Det samme gælder de to brug af -. Så jeg vil hellere bruge et enkelt pilsymbol fra HTML-tegnsættet, men som sædvanlig kan jeg ikke vise dette tegn i BBCode.

Det var en masse tekst om en enkelt sprogkonstruktion, så jeg tror jeg vil holde her for denne gang. Men med funktionsdefinitioner, funktionskald og betingelser kan man allerede en hel del.

Kommentarer (13)
sortSortér kommentarer
  • Ældste først
  • Nyeste først
  • Bedste først
#1 Bjarke Walling

Hvilke datatyper skal være til rådighed? Heltal (som jeg kan se du har brugt), kommatal?, brøker, komplekse tal måske, vektorer og matricer? Hvad med arrays/lister, hvor mængder vel er et mere passende koncept?

At oprette en mængde skal være så nemt som: {1, 2, 4, 8}. Hvad med en filter-funktion, der har følgende syntaks: { navn in mængde | betingelse }. F.eks. hvis man vil have ulige heltal fra 1 til 27: { x in N | x < 27 ^ ulige(x) }. Det vil selvfølgelig være bedre at bruge Unicode-tegnet for "tilhører mængde" end "in". Og måske skal betingelsen ikke syntaktisk være implicit, men stadig angives med kantede parenteser. Jeg har også introduceret en standardmængde N (alle positive heltal). Hvordan fortolkeren behandler uendelige mængder er så en anden interessant detalje, men måske er det ikke noget problem, hvis sproget er "lazy evaluated".

  • 0
  • 0
#2 Morten Kromberg

I APL, hvor der tit arbejdes med flerdimensionelle (numeriske) arrays, benyttes et par andre teknikker ud over dem som er beskrevet ovenfor, til at udtrykke conditionals.

Jeg forsøger at skrive lidt APL nedenfor, så får vi se om dette debat-forum kan klare Unicode. Jeg ber om undskyldning på forskud, hvis det ikke virker.

if y<3 then x=15 else x=13

Kan skrives:

[code=apl] x ← 13 + 2 × (y < 3) [/code]

Dette udtryk læses "x er 13 plus 2 gange y mindre end 3". Paranteserne er faktisk unødvendige i APL men er taget med for at gøre udtrykket "forståeligt for alle".

Alternativt kan man skrive:

[code=apl] x ← 13 15 [y<3] [/code]

Altså arrayet {13,15}, en to elements liste, indekseret ved resultatet af det logiske udsagn y<3. Der hvor udsagnet er falskt vælges element #0, hvor det er sandt vælges #1.

Begge teknikker kan selvfølgelig udvides til at vælge mellem flere end to alternativer. Man kan diskutere hvorvidt disse udregninger udtrykker logikken på en læsbar måde - eller om de er "kryptiske" i forhold til for eksempel if-then-else. I praksis vil jeg påstå at udregningerne tit svarer til den "matematiske logik" som ligger bagved udtrykkene.

Den store fordel ved disse metoder er at de uafhængige af antallet og størrelsen af dimensioner i y: De virker hvis y er et enkelt tal, men også hvis y er 3-(eller 15)-dimensional. x får samme form som y. Udtrykkene kan eksekveres parallelt, og bidrager til at APL er ret effektivt selv om det er fortolket: Selv om der en 1 million elementer i y skal der kun fortolkes en (meget kort) linie kode.

P.S. APL bruger naturligvis Unicode-tegnet ∊ for "tilhører mængde" :- )

  • 0
  • 0
#3 Torben Mogensen Blogger

Datatyper vil bestemt blive behandlet i et senere afsnit, og en form for sekvensstruktur skal der nok komme. Jeg er lidt usikker på, om {} er det bedste valg til notation, da de i reglen bruges til mængder, og sekvenser/lister er bestemt ikke mængder.

Jeg bruger selv tit Haskells listebyggere, der ligner det filterudtryk, du skriver. Men det er netop en af den slags avancerede strukturer, der går imod ideen om 1:1 mellem begreber og programkonstruktioner.

  • 0
  • 0
#4 Torben Mogensen Blogger

Jeg husker ideen om at gange med en sandhedsværdi fra mine BASIC dage (og har også set det brugt i mange C-programmer). Men jeg er ikke glad for at bruge heltal til sandhedsværdier -- jeg foretrækker klart en separat boolsk type, uanset om sproget er statisk eller dynamisk typet.

At lade et betinget udtryk være et opslag i en to-elements tabel med et boolsk indeks er en sød ide, men med mindre man beregner tabelelementerne dovent, risikerer man nemt uendelige beregninger eller uønskede sideeffekter.

  • 0
  • 0
#5 Jørgen Elgaard Larsen

[b]indledning[/b]

Efter at have kigget i min "Matematisk Formelsamling" fra gymnasietiden, er jeg enig i, at der ikke rigtig er noget med betingelser. Noget (udover "medfører"), der ligner lidt, er mængdelærens "hvorom det gælder", med notationen "|":

M = { x tilhører G | x < 5 }

Jeg ved godt, at det ikke er det samme som betingelse, men mine overvejelser tager udgangspunkt i, hvad gymnasieelever kan have set før.

Min svage erindring mumler noget om, at lærebøgerne i gymnasiet godt kunne finde på at definere funktioner som

f(x) = x^2 ; x > 0 -x +1 ; x <= 0

Det har jeg dog ikke kunnet verificere med formelsamlingen.

[b]Overvejelser[/b]

Det foreslåede format abs(x) = [0>x-> -x | x] har den ulempe, at det bruger "|".

Godt nok er vi, der er opvokset med c-afarter, vant til at læse dette som "eller", men for en gymnasieelev vil det betyde "absolut" eller "hvorom det gælder". Det er også ret kompakt og dermed uoverskueligt.

Jeg foretrækker klart noget i retning af den betingede funktionsdefinition, jeg nævner ovenfor. Fordelen er, at den er nemt overskuelig.

Ulempen ved en if/else er, at den kun kan have to muligheder. I virkeligheden passer en case/switch-konstruktion bedre til opgaven, og kan sagtens erstatte en "if" (f.x. har PostgreSQL kun CASE, ikke IF).

Jeg vil derfor foreslå noget i stil med: x = 15 ; y < 3 17 ; y > 25 13 ; otherwise

Tilsvarende:

abs(x) = x ; x >= 0 -x ; otherwise

Man kunne nærme det til naturligt sprog ved at erstatte ";" med "if":

x = 15 if y < 3 17 if y > 25 13 otherwise

Uanset hvad, er afslutningen på betingelsen, at der kommer et "otherwise"-led, som skal være obligatorisk.

  • 0
  • 0
#6 Morten Kromberg

Torben skriver:

jeg foretrækker klart en separat boolsk type

Jeg vil påstå at det er en smags-sag (måske kan jeg overbevises hvis du skrev noget om hvorfor du foretrækker dette). At bruge heltalsværdier giver meget større fleksibilitet til at lave beregninger hvor tests indgår.

opslag ... med et boolsk indeks ... er en sød ide, med mindre man beregner tabelelementerne dovent, risikerer man nemt uendelige beregninger eller uønskede sideeffekter

Jeg har svært ved at se hvad slags sideeffekter, der kan opstå - kan du uddype dette punkt? Med hensyn til hastighed, så er det meste arbejde med "boolske" arrays (også kaldet 1-bit integers :-)) optimeret i APL. På min laptop kan jeg behandle godt og vel 40 millioner elementer per sekund under udførelsen af [code=apl] 13 15[x<3] [/code]

Jeg ved ikke om "sød" er det riktige ord for disse teknikker; jeg synes måske "saftig" er bedre ;-). Jeg bruger "<-" for tilordning i de følgende eksempler, da det ikke ser ud til at forum'et kan lide mine APL venstrepile som Unicode tegn...

Lad os sige at vi vil give 10% rabat ved salg over 50 og 25% hvis vi når op over 75:

[code=apl] salg <- 52 47 60 97 19 76 rabat_pct <- (10×salg>50)+(15×salg>75) [/code]

Hvilket for en APL-bruger hurtigt leder tankerne i retning af:

[code=apl] rabat_pct <- 10 15 +.× 50 75 ∘.< salg [/code]

(Vektorproduktet af {10,15} og resultatet af "ydreproduktet" mellem {50,75} og salgstallene).

... Det er en lidt anden måde at tænke på - men når man er inde i tankegangen kan man virkelig hurtigt få udtrykt nogle beregninger. Den sidste formel ovenfor udtrykker efter min mening beregningen klarere end en loop rundt om en traditionel if-eller case- sætning ... Jeg vil også påstå at den er nemmere at vedligeholde: den kan RET hurtigt udvides til flere rabat-niveuaer og inviterer udvikleren til at indføre tabeller til at definere disse.

Definitionen af sandhedsværdier som "heltal" er helt nødvendig for at tillade denne form for beregninger.

  • 0
  • 0
#7 Torben Mogensen Blogger

Det foreslåede format abs(x) = [0>x-> -x | x] har den ulempe, at det bruger "|".

Godt nok er vi, der er opvokset med c-afarter, vant til at læse dette som "eller", men for en gymnasieelev vil det betyde "absolut" eller "hvorom det gælder". Det er også ret kompakt og dermed uoverskueligt.

Jeg kan se din pointe om, at | bruges til andet, f.eks. absolut værdi. Men at den kompakte form er uoverskuelig kan jeg ikke lige se. Et alternativ til | kunne være ;.

Det er iøvrigt ret nemt at udvide notationen til en multivejsforgrening i stil med

[x<0 -> -1 ; x=0 -> 0 ; 1]

  • 0
  • 0
#8 Torben Mogensen Blogger

Torben skriver:

jeg foretrækker klart en separat boolsk type

Jeg vil påstå at det er en smags-sag (måske kan jeg overbevises hvis du skrev noget om hvorfor du foretrækker dette). At bruge heltalsværdier giver meget større fleksibilitet til at lave beregninger hvor tests indgår.

Hvis man bruger heltal til boolske værdier har man mere end to forskellige værdier, og det kan give flere problemer:

  • Der er flere forskellige værdier, der alle betyder "sand" (eller alle betyder "falsk"), så sammenligning er ikke det samme som logisk ækvivalens.

  • Logiske operationer skal udvides til at håndtere alle heltal. Det sker oftest ved at gøre dem til bitvise operationer, men det betyder f.eks., at sand ^ sand = falsk, hvis de to sande værdier er f.eks. tallene 1 og 2 (jeg bruger her ^ som "og"). Alternativt skal man have særlige logiske operatorer, så p ^ q er 1, hvis både p og q er forskellig fra 0 osv.

  • Boolske værdier leder naturligt frem til typer, der kan være "det ene eller det andet", altså unions eller sumtyper.

opslag ... med et boolsk indeks ... er en sød ide, med mindre man beregner tabelelementerne dovent, risikerer man nemt uendelige beregninger eller uønskede sideeffekter

Jeg har svært ved at se hvad slags sideeffekter, der kan opstå - kan du uddype dette punkt?

Lad os sige, at vi skrev

f(x) g(x) [x<0]

Både f(x) og g(x) bliver beregnet, også den, der ikke bliver valgt. Hvis den ikke-valgte laver sideeffekter eller ikke terminerer, så er det et problem. Nontermination kan håndteres ved doven beregning, men doven beregning og sideeffekter passer dårligt sammen. Så kan man selvfølgelig også eliminere sideeffekter (ligesom Haskell), men det er en helt anden sag.

  • 0
  • 0
#9 Bjarke Walling

Datatyper vil bestemt blive behandlet i et senere afsnit, og en form for sekvensstruktur skal der nok komme. Jeg er lidt usikker på, om {} er det bedste valg til notation, da de i reglen bruges til mængder, og sekvenser/lister er bestemt ikke mængder.

Det er jeg helt enig i. Det jeg forestillede mig var at man kunne inkludere en mængde-type i sproget, hvor {} ville være en god notation.

Da jeg gik i gymnasiet lærte vi mængde-udvalg som { x tilhører X | udsagn om x }, der implicit har en betingelse efter den lodrette streg. Den konstruktion vil kun give mening at have med, hvis sproget inkluderer mængder. Til lister må der bruges en anden syntaks.

Jeg tænker at hvis man tager udgangspunkt i hvad gymnasieelever kender af begreber, så vil det være smart at have mængder og vektorer med. Jeg synes ikke sekvenser/lister er noget jeg rigtig brugte i matematik i gymnasiet.

  • 0
  • 0
#10 Deleted User

Jeg syntes, at et sprog, ikke må indeholde forudbestemt opløsning for floats - det bedste er, at kunne erklære et område, og en nøjagtighed, så brugeren har frit mulighed for at vælge område, og nøjagtigheden. Hvis hastigheden ikke er vigtigt, må alle beregninger gerne ske med såvel minimums, som maksimum værdi, så der tages højde for præcision, og en funktion kan oplyse, hvor præcis resultatet er. Ved indtastning, kan også angives en præcision og nøjagtighed - så man ikke regner bedre resultat ud, end man faktisk har. Med en funktion, kan +/- afvigelsen udskrives på variablen eller tallet. Hvert float, vil reelt fylde to floats i lageret (f.eks. min og max), for at holde styr på nøjagtighed. Det er meget brugbart i gymnasiet til fysik.

Heltal, kan enten være en "del" af floats, som ved basic. Eller, det kan erklæres som en type, der har et område (f.eks. -193..129) eller bare er en "integer", hvor integer betyder at tallet kan være vilkårligt højt, og vilkårligt lavt, og der afsættes plads til den nødvendige størrelse. Ønskes ikke at håndteres en "dynamisk" bredde på tal, må int, eller integer udelades, og i stedet må tallets definationsområde erklæres explicit.

  • 0
  • 0
#11 Torben Mogensen Blogger

Jeg syntes, at et sprog, ikke må indeholde forudbestemt opløsning for floats

Principielt er jeg enig, men gymnasieelever er vant til en lommeregners begrænsede nøjagtighed, så det er ikke en ting jeg vil insistere på, hvis det gør andre ting mere komplicerede. Jeg vil dog som udgangspunkt forvente en ret høj præcision af kommatal, og evt. ubegrænsede heltal. Man kunne endda lade sig inspirere af Scheme, hvor heltal og brøker af heltal er ubegrænsede, og først når man bruger en operation, der ikke giver en brøk som resultat, falder man tilbage på kommatal.

  • 0
  • 0
#12 Torben Mogensen Blogger

Jeg tænker at hvis man tager udgangspunkt i hvad gymnasieelever kender af begreber, så vil det være smart at have mængder og vektorer med. Jeg synes ikke sekvenser/lister er noget jeg rigtig brugte i matematik i gymnasiet.

Vektorer i en eller anden form skal bestemt med. Mængder er mere tvivlsom -- man bruger ikke mængder i gymnasiet så meget som man gjorde tidligere. Det er f.eks. min erfaring, at de fleste, der begynder på universitetet (selv med A-niveau i matematik) ikke kender notation i stil med {x \in M | x>0} (hvor \in er "tilhører" tegnet), og mange kan endda ikke regne med forenings- og fællesmængder.

Sekvenser/lister er på mange måder enklere end mængder, og hvis man kalder dem "opremsninger" eller lignende, så kan de nok forstås. En anden ting, der taler for sekvenser i stedet for mængder er, at det er forholdsvis nemt at modellere mængder med lister, mens det omvendte er mere kompliceret.

  • 0
  • 0
#13 Morten Kromberg

Torben skriver: Hvis man bruger heltal til boolske værdier har man mere end to forskellige værdier, og det kan give flere problemer:

  • Der er flere forskellige værdier, der alle betyder "sand" (eller alle betyder "falsk"), så sammenligning er ikke det samme som logisk ækvivalens

OK, her ser APL lidt anderledes på det: Det kun tallet 1, der betyder "sandt". Andre tal end 0 og 1 kan IKKE bruges i logiske udtryk (giver en typefejl).

Lad os sige, at vi skrev

f(x) g(x) [x<0]

Både f(x) og g(x) bliver beregnet, også den, der ikke bliver valgt

OK, jeg forstår problemet, tekniken er ikke velegnet hvis f eller g har side-effekter. Men opgaven gik ud på at vælge mellem to konstanter (13 eller 15). Hvis der nu er 1 million elementer, vil et loop omkring et if/then/else heller ikke være smart, så vil f og g udføres i gennemsnit 500,000 gange hver?

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