Uppgiften består i att skriva ett program som skall utföra symbolisk derivering. Det innebär att programmet skall läsa in en representation av ett matematiskt uttryck (funktion) och konstruera ett nytt matematiskt uttryck som är derivatan av det givna uttrycket med avseende på en given variabel. Till exempel skall programmet beräkna derivatan av x3 med avseende på x till 3x2.
Samtidigt skall programmet förenkla uttrycket så att det inte blir onödigt komplicerat. Derivatan av x2+4 skall t.ex. inte bli 2x1+0 utan 2x.
För det första skall du konstruera en abstrakt datatyp
expr
för att representera enkla matematiska uttryck.
Du kan inte använda ML-funktioner som indata till ditt program eftersom du skall utföra symbolisk derivering. D.v.s. ditt program måste kunna se hur dina matematiska uttryck är uppbyggda.
Du skall kunna representera följande slags uttryck: variabler (v), heltal (c), summor av uttryck (e1+e2), differenser av uttryck (e1-e2), produkter av uttryck (e1×e2) och potenser med heltalsexponent (ei).
För varje slags uttryck skall du ha:
Du ska dessutom skriva en funktion exprToString : expr ->
string
som givet ett uttryck returnerar en sträng som beskriver uttrycket. (En sådan funktion är också användbar under felsökningen.) Strängen måste vara riktig enligt vanliga matematiska regler, t.ex. skall multiplikation ha högre precedens än addition, men den behöver inte vara elegant. Om du vill slippa fundera på sådana saker som precedens för operatorer så kan du helt enkelt sätta ut parenteser runt alla deluttryck...
Du skall skriva en
funktion deriv : expr * string -> expr
sådan att om
f
representerar ett uttryck f och x
är namnet på en representerad variabelx så skall deriv(f,x)
beräkna ett uttryck som representerar derivatan av
f med avseende på x. Kortare och slarvigare uttryckt:
deriv
tar fram derivatan av sitt första argument med
avseende på sitt andra argument.
Funktionen deriv
får inte vara en primitiv till den abtrakta datatypen för matematiska uttryck.
Ditt program skall kunna derivera ett godtyckligt uttryck med avseende på en variabel. De resulterande uttrycken skall förenklas så långt det är möjligt genom att utnyttja enkla matematiska samband som t ex att x + 0 är x, att 1 × x är x, etc. En god strategi är att lägga förenklingen i konstruktorerna för uttryck, men det är också fullt möjligt att skriva en speciell funktion för att förenkla uttryck. Däremot skall man inte försöka göra deriveringen och förenklingen i samma funktion -- det blir bara rörigt.
Ditt program skall klara av åtminstone följande deriveringsregler.
dc/dx | = | 0 | Derivatan av en konstant eller en annan variabel än den vi deriverar med avseende på är 0. |
dx/dx | = | 1 | Derivatan av en variabel med avseende på sig själv är 1. |
d(u+v)/dx | = | du/dx+dv/dx | Derivatan av en summa är summan av derivatan av argumenten. |
d(u-v)/dx | = | du/dx-dv/dx | Derivatan av en differens är differensen av derivatorna av argumenten. |
d(u×v)/dx | = | u(dv/dx)+v(du/dx) | Derivatan av en produkt u*v är u gånger (derivatan av v) plus (derivatan av u) gånger v |
d(ui)/dx | = | i×ui-1(du/dx) | Derivatan av en potens ui är exponenten (i), multiplicerat med basen (u) upphöjt i exponenten minus 1, multiplicerat med derivatan av basen (u). |
Ditt program skall utnyttja åtminstone följande förenklingsregler.
Man kan tänka sig många ytterligare förenklingsregeler, t ex för distributivitet etc, men krångla inte till detta steg för mycket. Du behöver till exempel inte kunna förenkla 3+x+5 till 8+x.
Derivering av x×y med avseende på x:
exprToString(deriv(makeProd(makeSym("x"),makeSym("y")),"x"))
skulle kunna ha värdet "y"
. (Derivatan är just y
och exprToString
applicerat på representationen av
symbolen y borde ge just strängen "y"
.
Derivering av x2-5x+7 med avseende på x:
exprToString(deriv(makeSum(makeDiff(makePow(makeSym("x"),makeNum(2)), makeProd(makeNum(5),makeSym("x"))), makeNum(7)), "x"))
skulle kunna ha värdet "(2*x)-5"
. (Derivatan är
2x-5 och exprToString
applicerat på
representationen av det givna uttrycket borde ge något i stil med strängen
"(2*x)-5"
.
Notera att såväl namn på primitiver som strängrepresentation är sådant du själv väljer. Det är inte alls nödvändigt att det ska se ut exakt som ovan.
Du skall skriva dokumentation till ditt program. Förutom de vanliga specifikationerna till varje funktion som skall finnas i programkoden, så innebär det att du skall skriva ett separat dokument som beskriver hur ditt program fungerar och hur man skall använda det.
Dokumentet skall lämnas in som en pdf-fil (helst) eller en textfil. Intesom en Microsoft Word .doc-fil, TeX-källkod eller liknande.
Dokumentationen skall innehålla:
Det skall räcka att läsa dokumentationen för att förstå ditt program. Du kan inte utgå ifrån att läsaren också läst denna uppgiftsbeskrivning. Du kan alltså inte utlämna information från dokumentationen bara för att den finns i uppgiftsbeskrivningen. Å andra sidan behöver du inte ta med allt som finns i uppgiftsbeskrivningen. Instruktionerna för hur uppgiften skall genomföras behöver förstås inte finnas i programdokumentationen.
Dokumentationen är lika viktig som själva programmet. Är dokumentationen dålig så kommer du att få komplettering på uppgiften även om ditt program fungerar. Det är svårt att skriva en godkänd dokumentation som inte är minst två hela sidor lång.
htDet finns ett exempel taget från ett tidigare års inlämningsuppgift som visar hur dokumentationen kan se ut. Exemplet visar en bra dokumentation som väl uppfyller minimikraven utan att för den skull vara perfekt.
expr
kan se ut.
datatype
för uttrycken medan du testar och felsöker
-- det underlättar eftersom du får datastrukturerna utskrivna av ML. Prova att ladda programmet med abstype
då och då för att kontrollera att du inte av misstag infört någon kod som är beroende av datatypens utseende -- konstruktorer ("taggar")
till exempel -- i de funktioner som har hand om
deriveringen. Hela den egentliga lösningen av deriveringen skall alltså ligga utanför
datatypsdefinitionen -- denna skall enbart hantera
uttryck. Glöm inte att det skall stå abstype
när du lämnar in lösningen!
foo
anropar funktionen bar
. Sedan bestämmer du dig för att ta bort bar
och ändrar på alla ställen där bar
används, utom just i foo
som du glömmer att ändra. Så länge du inte startar om ML kommer bar
att finnas kvar och foo
fungerar fortfarande. När du startar om ML så försvinner definitionen av bar
och foo
slutar att fungera. Ju förr du upptäcker det, dess bättre.
deriv
ska få ett uttryck och en sträng och returnera ett
uttryck. exprToString
ska få ett uttryck och
returnera en sträng. Detta innebär bland annat att deriv
inte ska använda sig av exprToString
.
expr
ska vara definierad med hjälp av
abstype
.
expr
skall inte känna till något om
derivering. Det enda som får ligga mellan with
och
end
är primitiver för datatypen, dvs konstruktorer,
selektorer och predikat. (exprToString
kan räknas som primitiv om man vill.)