Programkonstruktion och datastrukturer. Moment 2 Funktioner och programutveckling. PKD 2012/13 moment 2 Sida 1 Uppdaterad

Relevanta dokument
Programkonstruktion och datastrukturer. Moment 2 Funktioner och programutveckling. PKD 2011/12 moment 2 Sida 1 Uppdaterad

Återanvändning av kod? Programkonstruktion. Moment 2 Introduktion till funktioner och programutveckling. Funktionsdefinitioner. Egna funktioner i ML

Programmeringsmetodik DV1 Programkonstruktion 1. Moment 2 Introduktion till funktioner och programutveckling

Programspråk. Programkonstruktion. Moment 1 Introduktion till programmering och programspråket Standard ML. Användning av funktionell programmering

Programkonstruktion och datastrukturer. Formell verifiering eller hur man bevisar att program gör rätt utan att testa dem

Programmeringsmetodik DV1 Programkonstruktion 1. Moment 4 Om rekursion. PK1&PM1 HT-06 moment 4 Sida 1 Uppdaterad

Programkonstruktion. Tentamen,

Programkonstruktion. Tentamen,

Summera godtyckligt antal tal. Programkonstruktion. Moment 4 Om rekursion. Fullständigt resonemang för summeringen. Analys av summeringsproblemet

Introduktion till formella metoder Programmeringsmetodik 1. Inledning

Programmering A. Johan Eliasson

C++ Funktioner 1. int summa( int a, int b) //funktionshuvud { return a+b; //funktionskropp } Värmdö Gymnasium Programmering B ++ Datainstitutionen

Omtentamen (del 1, 6 högskolepoäng) i Programkonstruktion och datastrukturer (1DL201)

Typsystem. Typsystem... Typsystem... Typsystem... 2 *

Föreläsning 2 Programmeringsteknik och C DD1316. Mikael Djurfeldt

Typsystem. DA2001 (Föreläsning 23) Datalogi 1 Hösten / 19

Programkonstruktion och datastrukturer. Moment 9 Om högre ordningens funktioner. PKD 2010/11 moment 9 Sida 1 Uppdaterad

Programkonstruktion. Tentamen,

Programmeringsmetodik DV1, Programkonstruktion del 1 Tentamen,

Programmeringsteknik med C och Matlab

1 Funktioner och procedurell abstraktion

Pascal... Pascal. Pascal... Pascal...

Användarhandledning Version 1.2

Alla datorprogram har en sak gemensam; alla processerar indata för att producera något slags resultat, utdata.

Objektorienterad Programmering (TDDC77)

Pascal. reserverade ord fördefinierade funktioner och procedurer egendefinierade funktioner, procedurer och objekt

Programmera i C Varför programmera i C när det finns språk som Simula och Pascal??

I Skapa Hej.java och skriv programmet. I Kompilera med javac Hej.java. I Rätta fel och repetera tills du lyckas kompilera ditt program

Programmeringsmetodik DV1 Programkonstruktion 1. Moment 8 Om abstrakta datatyper och binära sökträd

Objektorienterad programmering i Java I. Uppgifter: 2 Beräknad tid: 5-8 timmar (OBS! Endast ett labbtillfälle) Att läsa: kapitel 5 6

Programkonstruktion och datastrukturer. Moment 9 Om högre ordningens funktioner. PKD 2011/12 moment 9 Sida 1 Uppdaterad

AssiML. Användarbeskrivning

Föreläsning 3-4 Innehåll. Diskutera. Metod. Programexempel med metod

6. Ge korta beskrivningar av följande begrepp a) texteditor b) kompilator c) länkare d) interpretator e) korskompilator f) formatterare ( pretty-print

Introduktion till programmering D0009E. Föreläsning 5: Fruktbara funktioner

Tabeller. Programkonstruktion. Moment 8 Om abstrakta datatyper och binära sökträd. Implementering av tabellen. Operationer på tabellen

Grundläggande programmering med C# 7,5 högskolepoäng

Några inbyggda funktioner (med resultat!) Introduktion till programmering D0009E. Föreläsning 4: Villkor och rekursion. Modulus-operatorn.

Programmering i C++ En manual för kursen Datavetenskaplig introduktionskurs 5p

Introduktion till programmering SMD180. Föreläsning 2: Variabler, uttryck och satser

Föreläsning 3-4 Innehåll

Introduktion till programmering SMD180. Föreläsning 4: Villkor och rekursion

732G Linköpings universitet 732G11. Johan Jernlås. Översikt. Repetition. Felsökning. Datatyper. Referenstyper. Metoder / funktioner

Omgivningar. Omgivningar är viktiga eftersom de avgör vilka namn som är synliga och därmed dessas innebörd och de värden som är förknippade med dem.

TDP002 - Imperativ programmering

TDIU01 - Programmering i C++, grundkurs

Introduktion till programmering SMD180. Föreläsning 5: Fruktbara funktioner

Datatyper och kontrollstrukturer. Skansholm: Kapitel 2) De åtta primitiva typerna. Typ Innehåll Defaultvärde Storlek

DD1314 Programmeringsteknik

Tabeller. Programkonstruktion. Moment 8 Om abstrakta datatyper och binära sökträd. Specifikationer för tabellfunktionerna. Operationer på tabellen

Programkonstruktion och datastrukturer. Moment 4 Programmeringsmetodik och programvaruteknik. PKD 2011/12 moment 4 Sida 1 Uppdaterad

Visual Basic, en snabbgenomgång

Klassdeklaration. Metoddeklaration. Parameteröverföring

Programkonstruktion och datastrukturer. Moment 4 Programmeringsmetodik och programvaruteknik. PKD 2010/11 moment 4 Sida 1 Uppdaterad

Programmering = modellering

Objektorienterad programmering Föreläsning 8. Copyright Mahmud Al Hakim Agenda (halvdag)

TENTAMEN TDDB53. Programmering i Ada för MI (provkod TEN2) den 7 april 2010 kl Institutionen för datavetenskap, IDA Olle Willén mars 2010

Föreläsningsanteckningar, Introduktion till datavetenskap HT S4 Datastrukturer. Tobias Wrigstad

Datalogi I, grundkurs med Java 10p, 2D4112, Fiktiv tentamen, svar och lösningar och extra kommentarer till vissa uppgifter 1a) Dividera förs

Språket Python - Del 1 Grundkurs i programmering med Python

1/15/2013. DD1310/DD1314/DA3009 Programmeringsteknik. Lärandemål... Vilka läser kursen? ...fler lärandemål VARFÖR? Föreläsning 1

Programmeringsmetodik DV1 Programkonstruktion 1. Moment 9 Om högre ordningens funktioner. PK1&PM1 HT-06 moment 9 Sida 1 Uppdaterad

Tentamen i. TDDC67 Funktionell programmering och Lisp

Föreläsning 3: Typomvandling, villkor och val, samt textsträngar

DD1361 Programmeringsparadigm. Carina Edlund

Objektorienterad Programmering (TDDC77)

Tentamen i kurserna Beräkningsmodeller (TDA181/INN110) och Grundläggande Datalogi (TDA180)

Python. Python är, som Scheme, ett interpreterat språk men det finns kompilatorer för Python.

Programmering med Java. Grunderna. Programspråket Java. Programmering med Java. Källkodsexempel. Java API-exempel In- och utmatning.

Att skriva till och läsa från terminalfönstret

Programmering II (ID1019) :00-11:00

Dagens föreläsning. Repetition. Repetition - Programmering i C. Repetition - Vad C består av. Repetition Ett första C-program

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 3 Jonas Lindgren, Institutionen för Datavetenskap, LiU

TUTORIAL: KLASSER & OBJEKT

Python. Python är, som Scheme, ett interpreterat språk men det finns kompilatorer för Python.

Översikt över Visual Basic

DD1310/DD1314/DA3009 Programmeringsteknik LÄRANDEMÅL... Vilka läser kursen? ...FLER LÄRANDEMÅL. Föreläsning 1

Obligatorisk uppgift: Numerisk kalkylator

Algebra och rationella uttryck

Introduktion till programmering SMD180. Föreläsning 9: Tupler

TDDC77 Objektorienterad Programmering

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 3 Jonas Lindgren, Institutionen för Datavetenskap, LiU

Dagens föreläsning Programmering i Lisp. - Bindning av variabler (avs 14.6) fria variabler statisk/lexikalisk och dynamisk bindning

Programmera på riktigt

Programmering I Tobias Wrigstad fredag, 2009 augusti 28

Lite om felhantering och Exceptions Mer om variabler och parametrar Fält (eng array) och klassen ArrayList.

Grunderna i stegkodsprogrammering

F2 Datatyper och variabler. ID1004 Objektorienterad programmering Fredrik Kilander

Multipel tilldelning. Introduktion till programmering D0009E. Föreläsning 6: Iteration. while-satsen. Kom ihåg. Snurror kontra rekursion

Inlämningsuppgift MiniPlotter

Variabler och konstanter

Regression med Genetiska Algoritmer

Sammanfattningar Matematikboken Y

Datastrukturer, algoritmer och programkonstruktion (DVA104, VT 2015) Föreläsning 6

Numeriska Metoder och Grundläggande Programmering för P1, VT2014

Programmering, grundkurs, 8.0 hp HI1024, HI1900 etc., Tentamen TEN1. Måndagen den 10 januari 2011,

Objektorienterad programmering D2

EDAA20 Programmering och databaser. Mål komprimerat se kursplanen för detaljer. Checklista. Föreläsning 1-2 Innehåll. Programmering.

Sätt att skriva ut binärträd

Labb i Datorsystemteknik och programvaruteknik Programmering av kalkylator i Visual Basic

Transkript:

Programkonstruktion och datastrukturer Moment 2 Funktioner och programutveckling PKD 2012/13 moment 2 Sida 1 Uppdaterad 2012-11-07

Återanvändning av kod? String.substring("Hägg",size "Hägg"-3,3) > "ägg" Detta uttryck plockar ut ett visst antal tecken (3) ifrån slutet av en teckensträng ("Hägg"). Vill man plocka ut ett annat antal tecken eller plocka ur en annan sträng så måste man skriva om uttrycket med delar utbytta: String.substring("Ångström",size "Ångström"-5,5) > "ström" Att behöva skriva om likadan kod tar tid och leder lätt till misstag (som att bara byta ut 3 mot 5 på ena stället). Man vill göra koden oberoende av vilken sträng och teckenantal det gäller. Hur lösa detta problem? PKD 2012/13 moment 2 Sida 2 Uppdaterad 2012-11-07

Egna funktioner i ML Man kan definiera egna funktioner (eg. funktionsprocedurer) som är oberoende av speciella indata kan användas flera gånger utan kopiering ges indata varje gång de används. > fun last(s,n) = # String.substring(s,size s-n,n); val last = fn : string * int -> string Denna deklaration definierar en ny funktion last som tar fram tecken från slutet av en sträng! s och n är namnen på indata till funktionen. > last("hägg",3); val it = "ägg" : string Ett ML-program byggs upp av (flera) sådana funktionsdefinitioner. PKD 2012/13 moment 2 Sida 3 Uppdaterad 2012-11-07

Jämför med dataflödesdiagram fun last(s,n) = String.substring(s,size s-n,n); size - String.sub string last PKD 2012/13 moment 2 Sida 4 Uppdaterad 2012-11-07

Beräkning av funktionsanrop Ett funktionsanrop beräknas som om man bytt ut indatanamnen mot de verkliga indata. fun last(s,n) = String.substring(s,size s-n,n); last("hägg",3) > String.substring("Hägg",size "Hägg"-3,3) > String.substring("Hägg",4-3,3) > String.substring("Hägg",1,3) > "ägg" last("ångström",5) > String.substring("Ångström", size "Ångström"-5,5) > String.substring("Ångström",8-5,5) > String.substring("Ångström",3,5) > "ström" PKD 2012/13 moment 2 Sida 5 Uppdaterad 2012-11-07

fun add1 x = x+1; add1 3+add1(4*2) - > (3+1)+add1(4*2) - > 4+add1(4*2) - > 4+add1 8 - > 4+(8+1) - > 4+9 - > 13 add1(3*add1(4*2)) - > add1(3*add1 8) - > add1(3*(8+1)) - > add1(3*9) - > add1 27 - > 27+1 - > 28 Mer exempel... PKD 2012/13 moment 2 Sida 6 Uppdaterad 2012-11-07

Funktionsdefinitioner funktionskropp funktionsnamn formellt argument fun add1 x = x+1 Namnet add1 binds till det nya funktionsuttrycket av deklarationen. När man anropar funktionen (applicerar den på argument) så binds x till argumentet i anropet (det aktuella argumentet) medan funktionskroppen beräknas. x kallas för en bunden variabel. Funktionskroppen beräknas som om x bytts ut mot det aktuella argumentet. Funktionen sägs returnera det beräknade värdet. Valet av x som identifierare är godtyckligt. add1(4*2) - > add1 8 - > 8+1 - > 9 Funktionens typ bestäms av typerna hos det formella argumentet och funktionskroppen. Funktionen ovan har typen int -> int. PKD 2012/13 moment 2 Sida 7 Uppdaterad 2012-11-07

Hur vet man funktionens typ? I de flesta statiskt typade programspråk måste programmeraren ange typen för varje funktion genom att ange argumenttyper och värdetyp. I ML skulle man kunna skriva: fun last(s:string,n:int):string = String.substring(s,size s-n,n); men ML kan (nästan alltid) bestämma typen hos en funktion automatiskt genom typhärledning. PKD 2012/13 moment 2 Sida 8 Uppdaterad 2012-11-07

Typhärledning fun last(s,n) = String.substring(s,size s-n,n); String.substring har typen string*int*int -> string. Eftersom s är första argument till String.substring så måste s ha typen string. Eftersom n är sista argument till String.substring så måste n ha typen int. Därmed måste last ha argumenttypen string*int. Eftersom värdet av String.substring också är värdet av last så måste last ha värdetypen string. Alltså har last typen string*int -> string. PKD 2012/13 moment 2 Sida 9 Uppdaterad 2012-11-07

Typhärledning med dataflödesdiagram fun last(s,n) = String.substring(s,size s-n,n); string int size string intint int - string int int int String.sub string string str ing last Varje linje måste ha samma typ vid alla anslutningar! PKD 2012/13 moment 2 Sida 10 Uppdaterad 2012-11-07

Misslyckad typhärledning Om ett program är feltypat så misslyckas typhärledningen. > fun last(s,n) = String.substring(s,size s-n,s); Error-Type error in function application. Function: String.substring:string*int*int->string Argument: (s, size s - n, s) : string*int*string Reason: Can't unify int (*In Basis*) with string (*In Basis*) (Different type constructors) Found near String.substring (s, size s - n, s) Här har man skrivit fel och av misstag gett s som sista argument till String.substring. String.substring har typen string*int*int -> string. s är 1:a argument till String.substring så typen är string. s är 3:e argument till String.substring så typen är int. s kan inte vara int och string samtidigt programmet är feltypat. PKD 2012/13 moment 2 Sida 11 Uppdaterad 2012-11-07

Misslyckad typhärledning med dataflödesdiagram fun last(s,n) = String.substring(s,size s-n,s); konflikt!????? int size string intint int - string int int int String.sub string string str ing last PKD 2012/13 moment 2 Sida 12 Uppdaterad 2012-11-07

Oväntat resultat av typhärledning (1) Vilken typ har funktionen sumsquares? fun sumsquares(x,y) = x*x+y*y; PKD 2012/13 moment 2 Sida 13 Uppdaterad 2012-11-07

Oväntat resultat av typhärledning (2) Vilken typ har funktionen sumsquares? fun sumsquares(x,y) = x*x+y*y; * och + är båda överlagrade och har de alternativa typningarna int*int->int och real*real->real. Om ML inte kan avgöra typen på aritmetiska operationer så väljer den int! sumsquares får alltså typen int*int->int. Var avsikten att använda sumsquares på flyttal så får man typfel. Lösning: ange uttrycklig typ på funktionen fun sumsquares(x:real,y:real):real = x*x+y*y; eller bara på något lämpligt deluttryck så att typhärledning fungerar fun sumsquares(x,y) = x*(x:real)+y*y; PKD 2012/13 moment 2 Sida 14 Uppdaterad 2012-11-07

Program på fil Normalt skapar man sitt ML-program i en texteditor (t.ex. Emacs) och sparar på fil där namnet slutar med.sml. example.sml fun last(s,n) = String.substring(s,size s-n,n); Filer läses in i ML med kommandot use. (Egentligen är use också en funktion detta återkommer jag till i slutet av kursen.) Filen behandlas (nästan) som om texten i filen skrivits in för hand. > use "example.sml"; val last = fn : string * int -> string val it = () : unit > last("hägg",3); val it = "ägg" : string PKD 2012/13 moment 2 Sida 15 Uppdaterad 2012-11-07

Inuti programfilen... Programfiler innehåller typiskt en mängd deklarationer, men även uttryck kan finnas där. Funktioner (och namngivna värden) måste definieras innan de används. Semikolon mellan deklarationer kan utelämnas om ML genom förekomsten av nyckelord kan avgöra var en deklaration eller slutar och nästa börjar. Kommentarer (*... *) läggs in i programfiler för att förklara programkoden där. Mellanslag och radbrytningar kan fritt läggas in mellan symboler för att göra programkoden mera läslig. PKD 2012/13 moment 2 Sida 16 Uppdaterad 2012-11-07

ML-mod i Emacs Man kan köra ML inifrån Emacs. Detta är praktiskt bl.a. kan man redigera och köra ML-program ifrån samma verktyg (Emacs). Kommandot M-x sml-poly-ml startar Poly/ML i en buffert med namnet *sml*. I bufferten *sml* skriver du uttryck, deklarationer etc. När du trycker på Return skickas texten på samma rad till ML. Har du ett ML-program i en annan buffert kan du skicka programmet till ML med kommandot C-c C-b när du är i denna buffert. Man kan bläddra bland tidigare indata till ML med M-p och M-n. Mera information finns på kurswebben (välj Programvara i menyn). PKD 2012/13 moment 2 Sida 17 Uppdaterad 2012-11-07

Avbryta ML Ett ML-program som kör kan avbrytas med ^C (C-c C-c i Emacs, eller om det inte fungerar C-q C-c Return). PKD 2012/13 moment 2 Sida 18 Uppdaterad 2012-11-07

Abstrakt och konkret Med abstraktion i programmering menas att man döljer eller skjuter åt sidan vissa egenskaper hos något. Detta gör man för att förenkla programmeringen så att man inte skall behöva tänka på detaljer. Du vill åka buss från Polacksbacken till Flogsta. Då behöver du inte veta vilken buss (alltså vilket fordon) du skall åka med vilket kan vara olika från gång till gång utan du åker med busslinje 21. Linje 21 är en abstraktion av de bussar som faktiskt kör mellan (bl.a.) Polacksbacken och Flogsta. PKD 2012/13 moment 2 Sida 19 Uppdaterad 2012-11-07

Abstrakt och konkret (forts.) I programmering abstraherar man data genom att dölja hur data representeras. strängen "Hej, hopp" representeras egentligen som en följd av teckenkoder, t.ex. (med ISO 8859-1 kod) 72, 101, 106, 44, 32, 104, 111, 112, 112 flyttalet 3.12 representeras egentligen som två tal, en mantissa och en exponent, t.ex. 312 och 2, där innebörden är 312*10-2 Hur data verkligen ser ut döljs av programspråket. Är mantissan och exponenten 312 resp. 2 eller är de 0.312 och 1? Det spelar ingen roll det viktiga är hur man kan arbeta med dem. (Den konkreta) representationen har abstraherats bort. PKD 2012/13 moment 2 Sida 20 Uppdaterad 2012-11-07

Definitioner Bindning kan ses som att namnet definieras att betyda ett värde. Detta är en form av abstraktion som kallas definitionsabstraktion. Man bortser från det konkreta värdet och använder istället ett abstrakt namn, i syfte att ge enkla/meningsfulla namn åt värden (t.ex. Math.pi i stället för 3.14159265 underlättar läsning.) visa på en speciell användning av ett uttryck (t.ex. dagarpervecka eller maxantal i stället för 7. Man kan lätt ändra värdet i en användning utan att riskera påverka andra.) undvika dubbla beräkningar (t.ex. val a = 5*5; a*a; beräknar 5*5*5*5 med bara två multiplikationer) Symbolerna som utgör namn kallas identifierare. PKD 2012/13 moment 2 Sida 21 Uppdaterad 2012-11-07

(Rosens) namn What's in a name? That which we call a rose by any other name would smell as sweet. W. Shakespeare, Romeo och Julia Namnet som sådant saknar i de flesta programspråk betydelse. Det är bara den mänskliga läsaren av ett program som kan tolka namnet. Därför är det viktigt att ett tolkningen av ett namn faktiskt stämmer överens med innebörden av det som namnet används till. Man kan byta ut alla namn inom ett program (om det görs på ett konsekvent sätt) utan att påverka programmets funktion alls. (Detta gäller förstås inte namn som är definierade utanför själva programmet t.ex. Math.pi som är definierat i språket ML.) PKD 2012/13 moment 2 Sida 22 Uppdaterad 2012-11-07

Att deklarera Funktionsabstraktion fun last(s,n) = String.substring(s,size s-n,n); kallas för en funktionsabstraktion. Man har abstraherat bort konkreta data (t.ex. strängen "Hägg" och antalet 3) för att få ett mer generellt abstrakt uttryck. Beräkningar med konkreta data har också blivit mer abstrakt uttrycket String.substring("Hägg",size "Hägg"-3,3) ersätts av ett uttryck last("hägg",3), där den exakta metoden att utföra beräkningen är bortabstraherad. Endast namnet last används definitionsabstraktion igen! PKD 2012/13 moment 2 Sida 23 Uppdaterad 2012-11-07

Funktionsabstraktion i verkliga livet Du vill skicka ett intyg till CSN-kontoret i Uppsala. En metod är att be en kompis ta papperet och gå till CSN med det. En annan är att stoppa det i ett kuvert och lägga i en brevlåda Postens brevförmedling utför samma sak som din kompis men är mer abstrakt eftersom du inte behöver veta hur det går till att få papperet till CSN. mer generell eftersom du kan använda posten för att skicka papper vart som helst (din kompis vill kanske inte åka till Sundsvall för att lämna ett papper till CSN huvudkontor). PKD 2012/13 moment 2 Sida 24 Uppdaterad 2012-11-07

Exempel: Kalkylator för rationella tal Önskemål: Ett program som skall ta emot två rationella tal (bråktal) och ett av de fyra räknesätten, utföra den angivna uträkningen och visa resultatet. Programmet skall heta qcalc. Innan vi kan skriva programmet måste vi konstruera datastrukturer och algoritmer för den beskrivna uppgiften. Huvudprincip: stegvis förfining. Om problemet inte kan överblickas, bryt ned det i delproblem och tillämpa samma princip på dessa. Överblickbara problem kan kodas direkt. PKD 2012/13 moment 2 Sida 25 Uppdaterad 2012-11-07

Rationella tal Definition: tal som kan skrivas som en kvot av två heltal (täljare och nämnare) där nämnaren 0. Mängden av rationella tal betecknas Q. Räkneregler: x1 y1 x1*y2+y1*x2 x1 y1 x1*y1 + = * = x2 y2 x2*y2 x2 y2 x2*y2 x1 y1 x1*y2 y1*x2 x1 y1 x1*y2 = / = x2 y2 x2*y2 x2 y2 x2*y1 PKD 2012/13 moment 2 Sida 26 Uppdaterad 2012-11-07

Tupler i ML Ett rationellt tal består av två heltal som hör ihop. Grupper av data kan hållas ihop i en tupel (post). Tupler är konstruerade och inte grundläggande datatyper. Syntax i ML: (data1,data2,...,datan) n är minst 2. Motsvarande typ: typ1*typ2*...*typn Ex: (1,6) : int*int Ex: (1.0,"Hej",true) : real*string*bool Åtkomst till komponenter (fält): #n (där n är ett heltal) ger fält n. Ex: #2 (1.0,"Hej",true) > "Hej" (ML kan också använda poster med namngivna fält.) OBS! # i sig är ingen funktion man kan inte skriva t.ex. #(n+1) för att få ut fält nummer n+1. PKD 2012/13 moment 2 Sida 27 Uppdaterad 2012-11-07

Tupler kan ge problem med typhärledning. > fun doublepair x = (2* #1 x,2* #2 x); Error-Can't find a fixed record type.found near #1 Error-Can't find a fixed record type.found near #2 Static Errors ML kan inte veta om tupeln x skall ha två, tre eller flera komponenter och kan därför inte bestämma dess typ. Typhärledning går inte att genomföra. Genom att på lämpligt ställe uttryckligen ange typen av x så försvinner problemet. > fun doublepair (x:int*int) = (2* #1 x,2* #2 x); val doublepair = fn: int * int -> int * int Detta kan hanteras smidigare med mönster som vi återkommer till. PKD 2012/13 moment 2 Sida 28 Uppdaterad 2012-11-07

Datarepresentation Ett rationellt tal representeras naturligt som ett talpar (2-tupel) av heltal, int*int, där första talet är täljaren och det andra nämnaren. Alla 2-tupler av heltal representerar emellertid inte något rationellt tal det gör inga tupler där andra talet (nämnaren) är 0. Andra heltalet i varje par måste alltså vara skiljt från 0. Detta är en invariant ett villkor som måste bevaras av programmet. Räknesättet som skall utföras kan representeras av ett tecken, #"+", #"-", #"*" eller #"/". Även här krävs en invariant inget annat tecken får förekomma. Andra representationer är också tänkbara. PKD 2012/13 moment 2 Sida 29 Uppdaterad 2012-11-07

Problemlösning - stegvis förfining Kalkylator Beräkna täljare för addition Beräkna nämnare för addition Välj räknesätt: add.? I så fall, utför addition subtr.? I så fall, utför subtraktion mult.? I så fall, utför multiplikation div.? I så fall, utför division Beräkna täljare för subtraktion Beräkna nämnare för subtraktion Första förfiningssteget: skriv huvudfunktionen qcalc med typen (int*int)*(int*int)*char -> int*int PKD 2012/13 moment 2 Sida 30 Uppdaterad 2012-11-07

Minns ni algoritmerna? En algoritm är en beskrivning av en beräkning som är: 1) Mekanisk (skall inte kräva intelligens för att förstå) 2) Behöver ändliga resurser (måste bli färdig) 3) Deterministisk (det skall alltid vara entydigt vad som skall göras) 4) Aritmetiserad (problemet skall kunna representeras i termer av data ytterst i form av heltal) Ett program är (oftast) en algoritm, men algoritmer kan skrivas mer människonära just för att beskriva beräkningar. T.ex. med hjälp av Klartext Pseudokod Flödesscheman Dataflödesdiagram PKD 2012/13 moment 2 Sida 31 Uppdaterad 2012-11-07

Problemlösningen som algoritm En algoritm kan uttryckas i klartext. Det är viktigt att man uttrycker sig noga, speciellt att man är försiktig med syftningar och att man tydligt skiljer på varje steg. Algoritm för beräkning med rationella tal. Indata: två rationella tal x och y samt ett räknesätt. 1. Är räknesättet +? I så fall, addera x och y. 2. Är räknesättet -? I så fall, subtrahera y från x. 3. Är räknesättet *? I så fall, multiplicera x och y. 4. Är räknesättet /? I så fall, dividera x med y. Algoritm för addition av rationella tal: Indata: två rationella tal x1/x2 och y1/y2 1. Täljaren i summan är x1*y2+y1*x2, nämnaren är x2*y2. PKD 2012/13 moment 2 Sida 32 Uppdaterad 2012-11-07

Pseudokod Pseudokod liknar programkod, men eftersom en människa och inte en maskin skall tolka den så behöver den inte vara lika strikt som ett program. Uttryckssättet kan också anpassas efter behovet. algoritm calc(x,y,räknesätt) om räknesätt = + så utför addition(x,y) om räknesätt = - så utför subtraktion(x,y) om räknesätt = * så utför multiplikation(x,y) om räknesätt = / så utför division(x,y) slut calc PKD 2012/13 moment 2 Sida 33 Uppdaterad 2012-11-07

Flödesschema Ett flödesschema visar i vilken ordning delberäkningar skall göras och vilka val som finns. Start. räknesätt = + Ja. Addera x och y Stopp. Nej. räknesätt = Nej. etc... Ja. Subtrahera y från x Stopp. PKD 2012/13 moment 2 Sida 34 Uppdaterad 2012-11-07

Huvudfunktionen i kalkylatorprogrammet fun qcalc(x,y,opr) = if opr = #"+" then qadd(x,y) else if opr = #"-" then qsub(x,y) else if opr = #"*" then qmul(x,y) else qdiv(x,y); Tack vare invarianten för räknesättet behöver man inte testa för #"/" det är den enda återstående möjligheten. De ännu inte skrivna underprogrammen har kallats qadd etc. Här kodar vi qcalc först och underprogrammen sedan (top-down). Alternativt kan man koda underprogrammen först (bottom-up). PKD 2012/13 moment 2 Sida 35 Uppdaterad 2012-11-07

Funktioner för de fyra räknesätten fun qadd(x:int*int,y:int*int) = (#1 x * #2 y + #1 y * #2 x, #2 x * #2 y); fun qsub(x:int*int,y:int*int) = (#1 x * #2 y - #1 y * #2 x, #2 x * #2 y); fun qmul(x:int*int,y:int*int) = (#1 x * #1 y, #2 x * #2 y); fun qdiv(x:int*int,y:int*int) = (#1 x * #2 y, #2 x * #1 y); Dessa funktioner motsvarar nästa förfiningssteg. De har alla typen (int*int)*(int*int) -> int*int. Typangivelserna för x och y finns för att typhärledningen inte kan avgöra att det handlar om 2-tupler det måste vi tala om. PKD 2012/13 moment 2 Sida 36 Uppdaterad 2012-11-07

Exempel på räkning med rationella tal qcalc((1,2),(1,3),#"+") > if #"+" = #"+" then qadd((1,2),(1,3)) else if #"+" = #"-" then qsub((1,2),(1,3)) else if #"+" = #"*" then qmul((1,2),(1,3)) else qdiv((1,2),(1,3)) > if true then qadd((1,2),(1,3)) else > qadd((1,2),(1,3)) > (#1 (1,2) * #2 (1,3) + #1 (1,3) * #2 (1,2), #2 (1,2) * #2 (1,3)) > (1 * #2 (1,3) + #1 (1,3) * #2 (1,2), #2 (1,2) * #2 (1,3)) > (1 * 3 + #1 (1,3) * #2 (1,2), #2 (1,2) * #2 (1,3)) > (3 + #1 (1,3) * #2 (1,2), #2 (1,2) * #2 (1,3)) > > (5,6) PKD 2012/13 moment 2 Sida 37 Uppdaterad 2012-11-07

Ett sammansatt exempel Man kan förstås också använda funktionerna qadd, qsub, qmul och qdiv direkt för att utföra beräkningar: Medelvärdet av 1/2 och 3/2: qdiv(qadd((1,2),(3,2)),(2,1)) - > qdiv((#1(1,2)* #2(3,2)+ #1(3,2)* #2(1,2), #2(1,2)* #2(3,2)), (2,1)) - > qdiv((8,4),(2,1)) - > (#1(8,4)* #2(2,1),#2(2,1)* #1(8,4)) - > (8,8)...vilket är det samma som 1/1. (håll ordning på parenteserna...) PKD 2012/13 moment 2 Sida 38 Uppdaterad 2012-11-07

Definitionsabstraktion igen... visa på en speciell användning av ett uttryck. Om vi vill ändra tecknet som anger multiplikation till #"." dels måste man ändra i programmet (funktionen qcalc). dels måste man ändra i alla platser som anropar qcalc! Om vi glömmer det ena...eller ändrar där det inte skall ändras, så slutar programmet fungera. Lösning: namnge tecknen som anger olika räknesätt: val addition = #"+" val subtraktion = #" " val multiplikation = #"*" val division = #"/" PKD 2012/13 moment 2 Sida 39 Uppdaterad 2012-11-07

Kalkylatorprogrammet med namngivna konstanter fun qcalc(x,y,opr) = if opr = addition then qadd(x,y) else if opr = subtraktion then qsub(x,y) else if opr = multiplikation then qmul(x,y) else qdiv(x,y); Anropsexempel: qcalc((1,2),(1,3),multiplikation) > (1,6) Bara ett ställe (där bindningen av namnet sker) behöver ändras för att byta (t.ex.) multiplikationstecknet till #".". Dessutom blir programkoden mer läsbar! PKD 2012/13 moment 2 Sida 40 Uppdaterad 2012-11-07

etc. Exempel på räkning med namngivna konstanter qcalc((1,2),(1,3),addition) > qcalc((1,2),(1,3),#"+") > if #"+" = addition then qadd((1,2),(1,3)) else if #"+" = subtraktion then qsub((1,2),(1,3)) else if #"+" = multiplikation then qmul((1,2),(1,3)) else qdiv((1,2),(1,3)) > if #"+" = #"+" then qadd((1,2),(1,3)) else if #"+" = #"-" then qsub((1,2),(1,3)) else if #"+" = #"*" then qmul((1,2),(1,3)) else qdiv((1,2),(1,3)) > if true then qadd((1,2),(1,3)) else > qadd((1,2),(1,3)) PKD 2012/13 moment 2 Sida 41 Uppdaterad 2012-11-07

Olika representation av samma data Normalt är två saker lika om deras delar är lika och tvärtom. qcalc((1,2),(1,3),addition) > (5,6) qcalc((2,3),(1,6),addition) > (15,18) Nu är 5/6 = 15/18, men 5 15 och 6 18! Två olika representationer kan representera samma konkreta data. Detta leder till svårigheter när data skall jämföras i program. Hur lösa detta problem? PKD 2012/13 moment 2 Sida 42 Uppdaterad 2012-11-07

Lösningar på likhetsproblemet En lösning: Förfina datarepresentationen så att den blir unik t.ex. genom att kräva att rationella tal är maximalt förkortade (och att nämnaren är > 0) en ny invariant. (Programmen måste ändras!) En annan lösning: Skriv speciella jämförelseprogram, t.ex. fun qequal(x:int*int,y:int*int) = #1 x * #2 y = #2 x * #1 y; qequal((5,6),(15,18)) > #1 (5,6) * #2 (15,18) = #2 (5,6) * #1 (15,18) > 5 * #2 (15,18) = #2 (5,6) * #1 (15,18) > 5 * 18 = #2 (5,6) * #1 (15,18) > 90 = #2 (5,6) * #1 (15,18) > 90 = 6 * #1 (15,18) > 90 = 6 * 15 > 90 = 90 > true PKD 2012/13 moment 2 Sida 43 Uppdaterad 2012-11-07

Gungor och karuseller... Lösningen med förfinad datastruktur kräver extra beräkningar (gcd och förkortning) vid räkning med rationella tal, men kräver ingenting extra vid jämförelser. Lösningen med speciella jämförelseprogram kräver inget extra vid räkning med rationella tal, men kräver extra beräkningar vid jämförelser. Extra beräkningar tar tid. Den bästa lösningen beror på proportionen mellan räkning och jämförelser. (Men den ursprungliga representationen är dålig även av andra skäl nämnaren växer hela tiden...overflow ) PKD 2012/13 moment 2 Sida 44 Uppdaterad 2012-11-07

Kodgranskning och testning Vid programmering är risken stor att man gör misstag. När programmet skrivits måste man hitta (eventuella) felaktigheter. Syntaxfel, typfel o.dyl. upptäcks när programmet läses av datorn. Fel på algoritmer (logiska fel) och beräkningsfel måste upptäckas på annat sätt. Man korrekturläser (granskar) koden och tänker igenom att den gör den man tänkt sig. Man provkör (testar) koden och kontrollerar att resultatet blir det man förväntat sig. Ju tidigare man hittar ett fel desto bättre! Bäst är om programspråket är utformat så att möjligheten att skriva fel är liten. PKD 2012/13 moment 2 Sida 45 Uppdaterad 2012-11-07

Testfall Man måste välja ut data att provköra programmet med (testfall). qadd: 2/3 + 1/4 = 11/12 qsub: 2/3 1/4 = 5/12 qmul: 2/3 * 1/4 = 1/6 (man måste ta hänsyn till att samma tal kan representeras olika...) qdiv: 2/3 / 1/4 = 8/3 qcalc: Ett fall för var och en av hjälpfunktionerna (t.ex. fallen ovan). Vilka och hur många testfall man behöver beror på hur programmet ser ut och vad skall göra. Mer om det senare PKD 2012/13 moment 2 Sida 46 Uppdaterad 2012-11-07

Felsökning Om testerna ger fel resultat så måste man felsöka programmet för att förstå varför. Hitta buggen. Kontrollera att svaret verkligen är felaktigt! Förvissa dig om att (ev.) hjälpfunktioner gör rätt (dessa bör vara färdigtestade och felsökta först). Granska den anropade funktionen igen med speciell tanke på de indata som användes i testfallet. Utför beräkninen steg för steg för att se var felaktiga värden uppstår. (Det förekommer felsökningshjälpmedel där datorn kan göra steg-för-stegberäkningar. Vi måste dock göra det för hand.) PKD 2012/13 moment 2 Sida 47 Uppdaterad 2012-11-07

Felaktigt program fun qadd(x:int*int,y:int*int) = (#1 x * #2 y + #1 y * #2 x, #2 x * #2 y); fun qsub(x:int*int,y:int*int) = (#1 x * #2 y - #1 y * #2 x, #2 x * #2 y); fun qmul(x:int*int,y:int*int) = (#1 x * #1 y, #2 x * #2 y); fun qdiv(x:int*int,y:int*int) = (#1 x * #2 y, #2 x * #1 y); fun qcalc(x,y,opr) = if opr = #"+" then qadd(x,y) else if opr = #"+" then qsub(x,y) else if opr = #"*" then qmul(x,y) else qdiv(x,y); PKD 2012/13 moment 2 Sida 48 Uppdaterad 2012-11-07

Provkörning > qadd((2,3),(1,4)); val it = (11,12) : int * int > qsub((2,3),(1,4)); val it = (5,12) : int * int > qmul((2,3),(1,4)); val it = (2,12) : int * int > qdiv((2,3),(1,4)); val it = (8,3) : int * int > qcalc((2,3),(1,4),#"+"); val it = (11,12) : int * int > qcalc((2,3),(1,4),#"-"); val it = (8,3) : int * int > qcalc((2,3),(1,4),#"*"); val it = (2,12) : int * int > qcalc((2,3),(1,4),#"/"); val it = (8,3) : int * int FEL!!!! PKD 2012/13 moment 2 Sida 49 Uppdaterad 2012-11-07

Felsökning av det felaktiga programmet Kontrollera att svaret verkligen är felaktigt! JA! (Det skulle t.ex. ha kunnat vara rätt rationella tal, men uttryckt med andra täljare och nämnare som 5/6 och 15/18.) Förvissa dig om att (ev.) hjälpfunktioner gör rätt (dessa bör vara färdigtestade och felsökta först). JA! Granska den anropade funktionen igen med speciell tanke på de indata som användes i testfallet. AHA! if-grenen som anropar qsub testar för ett plustecken istället för ett minustecken PKD 2012/13 moment 2 Sida 50 Uppdaterad 2012-11-07

Mönstermatchning De flesta funktioner skrivs med kod för flera olika fall. Den grundläggande konstruktionen för falluppdelning är if-then-else. fun sign x = if x = 0 then 0 else if x < 0 then ~1 else 1; Falluppdelning som jämför argument med konstanter kan göras direkt i funktionsuttrycket klausuler fun sign 0 = 0 sign x = if x<0 then ~1 else 1 mönster När funktionen anropas går ML igenom klausulerna i tur och ordning och ser väljer den första klausul där argumenten passar. PKD 2012/13 moment 2 Sida 51 Uppdaterad 2012-11-07

Matchning av sammansatta datatyper Matchning kan också göras över formen på tupler. Identifierare binds till komponenterna i tuplerna. Tag t.ex. qdiv. Denna funktion kan skrivas fun qdiv((x1,x2),(y1,y2)) = (x1*y2,x2*y1) qdiv((1,2),(3,4)) - > (1*4,2*3) - > (4,6) x1, x2, y1 och y2 byts ut mot respektive 1, 2, 3 och 4. I praktiken använder men oftast (där det är möjligt) denna metod (snarare än #) för att komma åt komponenter i tupler eftersom det ger mycket mer lättläst kod. Dessutom slipper man oklarheten kring antalet element i tuplerna det blir helt klart att varje tupel innehåller 2 komponenter. PKD 2012/13 moment 2 Sida 52 Uppdaterad 2012-11-07

Funktioner med flera argument har bara ett Funktioner i ML har egentligen alltid ett argument. Om funktionen ser ut att ha flera argument, t.ex. fun last(s,n) = last("hägg", 3) > "ägg"...så konstrueras vid anropet egentligen en tupel av argumenten och skickas som ett argument till funktionen. Formen hos de formella argumenten i funktionsdefinitionen är egentligen bara mönstermatchning i funktionens enda riktiga argument. Funktionstypen string*int -> string i detta exempel visar också det argumentet är en string*int-tupel. Av praktiska skäl säger man ofta ändå att last har två argument. PKD 2012/13 moment 2 Sida 53 Uppdaterad 2012-11-07

Matchning både på form och konstanter En signum-funktion för rationella tal: fun qsign((0,_)) = 0 qsign((x1,x2))= if x1*x2<1 then ~1 else 1; _ (understrykningstecken) kan användas istället för identifierare när man inte är intreserad av argumentets värde. Anropet qsign((0,4)) qsign((~2,3)) matchar första klausulen andra klausulen De dubbla parenteserna gör ingen skillnad mot enkla parenteser, men används för att betona att funktionen är tänkt att ha ett argument som är en 2-tupel. PKD 2012/13 moment 2 Sida 54 Uppdaterad 2012-11-07

Allmän form hos funktionsdefinitioner fun namn mönster1 = uttryck1 namn mönster2 = uttryck2... namn mönstern = uttryckn; Varje mönster är en konstant, en identifierare, symbolen _ eller skelettet till en datastruktur (som tupler) där komponenterna i sin tur är mönster. Alla mönster måste ha samma typ (funktionens argumenttyp). Alla uttryck måste ha samma typ (funktionens värdetyp). PKD 2012/13 moment 2 Sida 55 Uppdaterad 2012-11-07

Se upp med matchning 1 Det är viktigt att klausulerna kommer i rätt ordning! fun sign x = if x<0 then ~1 else 1 sign 0 = 0 sign 0 >...?? PKD 2012/13 moment 2 Sida 56 Uppdaterad 2012-11-07

Se upp med matchning 2 När man använder matchning mot datatyper där värdena är i form av identifierare (t.ex. bool) kan små felskrivningar ge syntaktiskt korrekta program med helt annorlunda beteenden! fun boolname true = "Sant" boolname false = "Falskt"; boolname true > "Sant" boolname false > "Falskt" Låt oss göra en liten felskrivning: fun boolname truw = "Sant" boolname false = "Falskt"; boolname true > "Sant" boolname false > "Sant" Varför?? PKD 2012/13 moment 2 Sida 57 Uppdaterad 2012-11-07

Se upp med matchning 3 Vid ett funktionsanrop måste argumenten matcha någon klausul: fun nametobool "Sant" = true nametobool "Falskt" = false; nametobool "Sant" > true nametobool "Falskt" > false nametobool "Kanske" > exekveringsfel (Match) I detta fall får man en varning när funktionen definieras. > fun nametobool "Sant" = true nametobool "Falskt" = false; Warning-Matches are not exhaustive. Found near fun nametobool "sant" = true nametobool "falskt" = false val nametobool = fn: string -> bool PKD 2012/13 moment 2 Sida 58 Uppdaterad 2012-11-07

Catchall -klausuler För att undvika exekveringsfel pga att ingen klausul matchar kan man lägga in en sista klausul som säkert matchar alla argument: fun talnamn 1 = "Ett" talnamn 2 = "Två" talnamn 3 = "Tre" talnamn _ = "Många"; Eller t.o.m. fun talnamn 1 = "Ett" talnamn 2 = "Två" talnamn 3 = "Tre" talnamn x = if x <= 0 then "Lite" else "Många"; PKD 2012/13 moment 2 Sida 59 Uppdaterad 2012-11-07

Kalkylatorprogrammet med matchning fun qadd((x1,x2),(y1,y2)) = (x1*y2 + y1*x2, x2*y2); fun qsub((x1,x2),(y1,y2)) = (x1*y2 - y1*x2, x2*y2); fun qmul((x1,x2),(y1,y2)) = (x1*y1, x2*y2); fun qdiv((x1,x2),(y1,y2)) = (x1*y2, x2*y1); fun qcalc(x,y,#"+") = qadd(x,y) qcalc(x,y,#"-") = qsub(x,y) qcalc(x,y,#"*") = qmul(x,y) qcalc(x,y,_) = qdiv(x,y); Fördelar: Mycket mera läsligt. Entydiga tupeltyper typdeklarationer behövs inte. Nackdel: Namngivna värden kan inte användas. Varför? PKD 2012/13 moment 2 Sida 60 Uppdaterad 2012-11-07

Svar på några frågor fun boolname truw = "Sant" boolname false = "Falskt"; truw är inte ett värde utan en identifierare som vilken som helst. Den kommer alltså att matcha varje argument till funktionen även false och samtidigt bindas till argumentet. Alltså: boolname false > "Sant" Av samma skäl kan inte namngivna värden användas i matchning de skulle matcha alla argument och bindas om. val addition = #"+" val subtraktion = #" " fun qcalc(x,y,addition) = qadd(x,y) qcalc(x,y,subtraktion) = qsub(x,y) qcalc((1,2),(1,3),subtraktion) > > qadd((1,2),(1,3)) > (5,6) PKD 2012/13 moment 2 Sida 61 Uppdaterad 2012-11-07

Case-uttryck Matchning väljer olika fall beroende på argumenten till en funktion. Matchning kan också användas för att välja olika fall beroende på värdet av ett uttryck. fun dummultiplikation(x,y) = case x*y of 1 => "Ett" 2 => "Två" 3 => "Tre" 4 => "Fyra" _ => "Mycket"; dummultiplikation(2,2) > "Fyra" dummultiplikation(2,3) > "Mycket" Liksom i if-then-else så beräknas inte uttrycken i case-klausulerna förrän man vet vilket alternativ som valts. PKD 2012/13 moment 2 Sida 62 Uppdaterad 2012-11-07

Bindning i case-mönster fun pratsammultiplikation(x,y) = case x*y of 1 => "Ett" 2 => "Två" 3 => "Tre" 4 => "Fyra" z => Int.toString z ^ "."; pratsammultiplikation(2,3) > case 2*3 of 1 => "Ett" 2 => "Två" > case 6 of 1 => "Ett" 2 => "Två" > Int.toString 6 ^ "." > "6" ^ "." > "6." Identifierare i mönstren byts alltså ut i klausulerna mot uttrycket efter case på liknande sätt som bundna identifierare byts mot argumenten i funktionsanrop. PKD 2012/13 moment 2 Sida 63 Uppdaterad 2012-11-07

Bindningsräckvidd Vi har sagt att bundna variabler i funktionskroppen byts ut mot respektive aktuella argument när en funktion anropas. I verkligheten är det lite mer komplicerat. fun pratsammultiplikation2(x,y) = (case x*y of 1 => "Ett" 2 => "Två" 3 => "Tre" Räckvidden för denna bindning omfattar inte denna klausul 4 => "Fyra" x => Int.toString x ) ^ "." Vid ett anrop pratsammultiplikation(2,3) byts inte x ut mot 2 i den sista case-klausulen. Bindningen av x i case-uttrycket skuggar den första bindningen. Räckvidden för bindningen av x i funktionsdefinitionen innehåller inte sista case-klausulen. PKD 2012/13 moment 2 Sida 64 Uppdaterad 2012-11-07

Vad händer om variabler binds igen? fun pratsammultiplikation2(x,y) = (case x*y of 1 => "Ett" 2 => "Två" 3 => "Tre" 4 => "Fyra" x => Int.toString x) ^ "."; pratsammultiplikation(2,3) > (case 2*3 of... x => Int.toString x)^"." > (case 6 of... x => Int.toString x)^"." > Int.toString 6 ^ "." > "6" ^ "." > "6." Den nya bindningen av x skuggar den gamla. Räckvidden av det första x omfattar inte Int.toString x. PKD 2012/13 moment 2 Sida 65 Uppdaterad 2012-11-07

Bindningsomgivningar Ett annat sätt att förklara bindningar som bättre stämmer med vad som faktiskt händer i datorn är med bindningsomgivningar. Man håller reda på bindningarna och byter namn mot värde först när variabeln skall beräknas. pratsammultiplikation(2,3) > (case x*y of...)^"." [x > 2, y > 3] > (case 2*y of...)^"." [x > 2, y > 3] > (case 2*3 of...)^"." [x > 2, y > 3] > (case 6 of...)^"." [x > 2, y > 3] > (Int.toString x [x > 6]) ^"." [x > 2, y > 3] > (Int.toString 6 [x > 6]) ^"." [x > 2, y > 3] > "6"^"." [x > 2, y > 3] > "6." Bindningsomgivningen [x > 2, y > 3] (inte en del av MLsyntaxen!) anger att x och y tillfälligt är bundna till heltalen 2 och 3. PKD 2012/13 moment 2 Sida 66 Uppdaterad 2012-11-07

Bindningsordningen är viktig Alla namn (inklusive funktioner) måste bindas innan de används. Har man skrivit funktionen add1 med definitionen fun add1 x = x+1; och add2 med definitionen fun add2 x = add1(add1 x); Så måste add2 definieras efter add1 annars klagar ML på att add1 är obunden. > fun add2 x = add1(add1 x); Error-Value or constructor (add1) has not been declared Found near add1 (add1 x) Error-Value or constructor (add1) has not been declared Found near add1 (add1 x) PKD 2012/13 moment 2 Sida 67 Uppdaterad 2012-11-07

Bindning är inte tilldelning En ombindning av ett namn skuggar ev. tidigare bindingar av namnet, men ändrar dem inte. > val x = 2; x binds. val x = 2 : int > fun addx n = n+x x är en fri variabel i addx. val addx = fn : int -> int > addx 0; val it = 2 : int > val x = 4; x binds om... val x = 4 : int > addx 0; val it = 2 : int men det påverkar inte addx! addx använder den definition av x som gällde när addx själv definierades! Skall ombindningen av x märkas vid anrop av addx måste även addx definieras om. PKD 2012/13 moment 2 Sida 68 Uppdaterad 2012-11-07

Bindning är inte tilldelning (forts.) Samma sak gäller ändring av funktionsdefinitioner. > fun add1 x = x+2;...slant på tangentbordet... val add1 = fn : int -> int > fun add2 x = add1(add1 x); val add2 = fn : int -> int > add2 3; val it = 7 : int...hoppsan... > fun add1 x = x+1; Rättad definition. val add1 = fn : int -> int > add1 3; val it = 4 : int Ok nu. > add2 3; val it = 7 : int Fortfarande fel! Även add2 måste definieras om när add1 ändras Lösning på detta problem: Ändra inte en funktion direkt i Poly/ML, utan lägg programmet i en fil och läs in hela filen efter ändring. PKD 2012/13 moment 2 Sida 69 Uppdaterad 2012-11-07

Funktionsvärda uttryck Funktioner i ML är första klassens objekt och kan beräknas med t.ex. if-then-else precis som andra värden. Alternativ qcalc: fun qcalc(x,y,opr) = (if opr = #"+" then qadd else if opr = #"-" then qsub else if opr = #"*" then qmul else qdiv) (x,y); qcalc((1,2),(1,3),#"+") > (if #"+"= #"+" then qadd else...)((1,2),(1,3)) > (if true then qadd else...) ((1,2),(1,3)) > qadd((1,2),(1,3)) > (5,6) PKD 2012/13 moment 2 Sida 70 Uppdaterad 2012-11-07

Anonyma funktioner Man kan skriva funktionsuttryck direkt där de skall användas utan separat definition en anonym funktion. Detta är ibland praktiskt. (fn x => x*x) 4 > 4*4 > 16 Vi utvidgar qcalc med medelvärdesoperation (a), men utan att definiera någon separat funktion för beräkningen: fun qcalc(x,y,opr) = (if opr = #"a" then fn (x,y) => qdiv(qadd(x,y),(2,1)) else if opr = #"+" then qadd ) (x,y); qcalc((1,2),(3,2),#"a") > (8,8) (Argumentnamnen till den anonyma funktionen, x och y, är samma som i huvudfunktionen. Vilken är räckvidden? Problem? Lämpligt?) PKD 2012/13 moment 2 Sida 71 Uppdaterad 2012-11-07

Funktionstyper är inte likhetstyper Man kan inte jämföra om två funktionsvärden med = eller <>! Anledningen är att det kan vara mycket svårt ja principiellt omöjligt att avgöra om två funktioner är lika. Är t.ex. (fn x => 2*x) = (fn x => if x<0 then x*2 else x+x) ja, det är det, men hur skall man kunna veta det? (Att man i allmänhet inte kan avgöra ifall två funktioner är lika är ett fundamentalt resultat som Turing visade.) Av liknande skäl kan man inte få ett funktionsvärde utskrivet ML svarar bara fn och med typen förstås. > qcalc; val it = fn : (int*int)*(int*int)*char->int*int PKD 2012/13 moment 2 Sida 72 Uppdaterad 2012-11-07

Syntaktiskt socker if B then E1 else E2 är exakt samma sak som case B of true => E1 false => E2 case E of P1=>E1 P2=>E2... Pn=>En är exakt samma sak som (fn P1=>E1 P2=>E2... Pn=>En) E fn P1=>E1 P2=>E2... Pn=>En är exakt samma sak som fn x => case x of P1=>E1 P2=>E2... Pn=>En Med syntaktiskt socker menas att man inför en form av uttryck som är ett enklare sätt att uttrycka något som redan finns i språket. if-then-else är onödig och case och funktionsmatchning utbytbara! PKD 2012/13 moment 2 Sida 73 Uppdaterad 2012-11-07