PK -08/09: Inlämningsuppgift 1

Inledning

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.

Uttryck

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:

  • en konstruktor
  • ett predikat som "känner igen" det slaget av uttryck
  • en uppsättning selektorer för deluttrycken

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...

Derivering

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.

Deriveringsregler

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).

Förenklingsregler

Ditt program skall utnyttja åtminstone följande förenklingsregler.

  • En summa, differens eller produkt av heltal skall förenklas till ett heltal.
  • En potens av heltal skall förenklas till ett heltal där så är möjligt. 22 skall alltså förenklas till 4, medan 2-2 inte skall förenklas eftersom 0,25 inte är ett heltal.
  • Summan av ett uttryck e och 0 (eller vice versa) skall förenklas till e.
  • Differensen av ett uttryck e och 0 skall förenklas till e.
  • Produkten av ett uttryck e och 0 (eller vice versa) skall förenklas till 0 medan produkten av ett uttryck e och 1 (eller vice versa) skall förenklas till e.
  • Potensen av ett uttryck e och 0 skall förenklas till 1 medan potensen av ett uttryck e och 1 skall förenklas till e.

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.

Exempel

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.

Dokumentation

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:

  1. titel, författarnamn, uppgift om att detta är inlämningsuppgift 1 på denna kurs detta år, innehållsförteckning, inledning med sammanfattning av vad programmet gör.
  2. En "användarbeskrivning": en anvisning för hur man praktiskt använder ditt program för att lösa utföra symbolisk derivering, inklusive körexempel.
  3. En "programdokumentation": en beskrivning av hur ditt program egentligen fungerar:
    • beskrivning av hur din abstrakta datatyp för uttryck fungerar, hur du har representerat uttrycken och hur man skall använda den abstrakta datatypen
    • beskrivning av hur deriveringen fungerar, vilka uttryck som kan deriveras och hur man skall använda deriveringsfunktionen.
    • beskrivning av hur förenklingen fungerar, vilka uttryck som kan förenklas och hur man använder förenklingsfunktionen (om den är en separat funktion).
    • Beskrivning av vad de olika delfunktionerna gör och hur programflödet ser ut (alltså hur funktionerna anropar varandra). Det skadar inte att ge en beskrivning av algoritmen här.
  4. Beskrivning av kända brister hos programmet. Det kan vara sådant som du själv tycker är mindre lyckat gjort, funktioner enligt uppgiften som du trots tappra försök inte lyckats få till ordentligt eller saker som du tycker programmet borde göra fastän det inte krävs i uppgiften.

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.

ht

Det 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.

Inlämning

  • Inlämningsuppgiften skall lösas individuellt! Du får givetvis diskutera uppgiften och olika lösningsmetoder med kurskamrater, men du skall själv ha konstruerat din lösning.
  • Lösningen skall lämnas in i två delar -- en ML-fil och en dokumentationsfil. Överst i ML-filen skall du lägga en kommentar med kursens namn, texten "Inlämningsuppgift 1", datum och ditt namn. Dokumentationsfilen skall innehålla samma uppgifter i rubriken.
  • Du skall följa kodstandarden, bl.a. skall du skriva funktionsdeklarationer för alla funktioner du definierar och varianter för alla rekursiva funktioner. Detta är ännu viktigare än för labbarna. Eftersom du har nästan full frihet att utforma din lösning som du vill -- t.ex. beträffande vilka hjälpfunktioner du inför -- så är det mycket viktigt att assistenterna snabbt kan sätta sig in i vad din kod gör. Där spelar bra funktionsspecifikationer en viktig roll.
  • Lämna in din lösning enligt anvisningarna senast den 2:a mars klockan 08:00!

Tips

Några praktiska tips som kommer att underlätta utförandet av uppgiften.
  • Läs om syntaxträd i OH-bilderna från föreläsning 7 för idéer om hur datatypen expr kan se ut.
  • Skriv så långt möjligt dokumentationen -- både funktionsspecifikationerna och den externa dokumentationen -- innan du börjar skriva programmet. Det vill säga: Tänk igenom hur du skall lösa uppgiften, skriv sedan dokumentationen och sist programmet.
  • Skriv den abstrakta datatypen först (inklusive alla primitiver) innan du skriver resten av koden.
  • Använd 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!
  • Starta om ML då och då så att du är säker på att du inte av misstag använder gamla definitioner. Antag att du i funktionen 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.
  • Be om hjälp när du kör fast. Antingen via mail eller genom att söka upp din assistent eller föreläsare. Det är inte meningen att ni ska sitta i timtal och jaga en och samma bug. Om du å andra sidan inte skrivit ner hur du tänkt göra, typ, etc, så är det det första du kommer att bli ombedd att göra, så det är ingen mening att fråga om kodhjälp om du inte gjort det (annat än allmänna frågor förstås).

Vanliga fallgropar

  • Se till att du har rätt typ på dina två huvudfunktioner. 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.
  • Datatypen expr ska vara definierad med hjälp av abstype.
  • Datatypen 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.)
  • Alla funktioner och hjälpfunktioner som hör till deriveringen ska ligga utanför datatypdefinitionen.
  • Glöm inte att följa kodstandarden!.
  • Glöm inte att följa minneslistan för programutveckling!.