Detta dokument utgör det första studiematerialet i kursen Programmeringsteknik, del A. Avsikten är att Du ska läsa och arbeta med dokumentet vid terminalen. Handledare kommer att finnas tillgängliga hela tiden, men avsikten är att ni ska arbeta självständigt två och två. Fråga dock någon handledare så fort ni kör fast; det är ingen idé att sitta och planlöst försöka gissa om man inte alls förstår. Speciellt bör ni tillkalla en handledare om ni tycker att ni gör som i detta dokument men får felmeddelanden ni inte förstår. Det är omöjligt att här diskutera alla de fel man kan göra; se till att få hjälp så ni kommer vidare!
Om en av er är datorvan och den andre nybörjare, så låt den ovane sköta skrivandet och tryckandet. Man lär sig inte av att sitta bredvid och titta på!
Däremot är det troligt att ni vid flera tillfällen kommer att kunna göra saker genom att kopiera de instruktioner som ges utan att ni förstår ``hur det fungerar''. Detta är avsiktligt; meningen är att ni ska bekanta er med det program vi ska använda oss av under kursen. Närmare förklaringar ges under de kommande veckorna.
Det kan vara tröttande att läsa detta dokument om typsnittet är för litet (dvs bokstäverna för små). Om det är så, öppna Edit-menyn och välj Preferences. I det fönster som öppnas välj Fonts under kategori Appearence. Välj slutligen lämplig storlek under Size på två ställen (både Variable Width Font för löpande text och Fixed Width Font för kod). Själv tycker jag storleken 18 punkter är lagom.
Meningen är att ni ska hinna arbeta igenom materialet under en förmiddag. Den viktigaste delen av arbetet är att besvara de frågor som förekommer i stor mängd med följande utseende:
fraggel33> hugs
Här och i fortsättningen använder vi konventionen att understruken text är användarens inmatning medan text utan understrykning har skrivits av systemet. Ni bör också efterhand som ni läser texten verkligen göra det som beskrivs. Om ni inte redan startat hugs, gör det nu! Som svar får ni se hugs' välkomsthälsning:
__ __ __ __ ____ ___ _________________________________________
|| || || || || || ||__ Hugs 98: Based on the Haskell 98 standard
||___|| ||__|| ||__|| __|| Copyright (c) 1994-2001
||---|| ___|| World Wide Web: http://haskell.org/hugs
|| || Report bugs to: hugs-bugs@haskell.org
|| || Version: February 2001 _________________________________________
Haskell 98 mode: Restart with command line option -98 to enable extensions
Reading file "/usr/pd/share/hugs2001/hugs/lib/Prelude.hs":
Hugs session for:
/usr/pd/share/hugs2001/hugs/lib/Prelude.hs
Type :? for help
Prelude>
Vi bryr oss för tillfället inte om det som står här utan koncentrerar oss på
sista raden där markören (dvs den plats där text vi skriver hamnar) finns.
Först på raden finner vi texten Prelude> , vilket
indikerar att hugs är beredd att ta emot ett kommando. Vad ordet
Prelude betyder får vi vänta med att förklara. Det enklaste
kommandot är att bara skriva ett uttryck, varvid hugs beräknar och
skriver ut uttryckets värde:
Prelude> 4+5*6 34 Prelude>Därefter är, som antyds av markören, systemet berett på ett nytt kommando. Vi kan upprepa denna interaktion hur många gånger som helst, som i nedanstående dialog.
Prelude> 4*(8-5)+3 15 Prelude> 3.5/(3+4)*4.5 2.25 Prelude> sin 1.57 1.0 Prelude>
Prelude> 3+5*
ERROR: Syntax error in expression (unexpected end of input)
Prelude> 4/0
Program error: {primDivDouble 4.0 0.0}
Prelude>
I det första fallet är uttrycket syntaktiskt fel eftersom det
inte finns någon högeroperand till multiplikationen. I det andra
fallet försöker vi dividera med noll vilket ger fel vid beräkningen.
Att känna igen olika sorters fel och förstå vad som orsakar dem
tillhör det som tar mest tid i början. Vi kommer att återkomma
till detta men kan inte säga mer nu.
Prelude> :q [Leaving hugs] fraggel33>Alla kommandon till hugs utom att ge ett uttryck för beräkning inleds med kolon. Starta hugs igen och läs igenom hälsningsmeddelandet litet noggrannare.
Vi har sett hur man kan använda hugs för enkla beräkningar. Men det är bara början! Vi kan definiera funktioner som bygger ut systemets förmåga. Som ett första exempel betraktar vi växlingsproblemet ovan. Om vi många gånger vill beräkna hur många engelska pund som ett givet belopp i kronor motsvarar så kan vi definiera en funktion som uttrycker detta. Att definiera en funktion sker i flera steg:
pund(kr) = kr/12.7775
pund(kr) = kr/12.7775och spara detta i en fil med namnet ex1.hs. Se till att filen är i samma katalog som den där Du startade hugs. Gå eventuellt ur hugs, kontrollera (med Unixkommandot ls) att det finns en fil med namnet ex1.hs och kontrollera (med Unixkommandot more) att denna fil har rätt innehåll.
Prelude> :l ex1.hs
Reading file "ex1.hs":
Hugs session for:
/usr/pd/lib/Hugs98May/hugs/lib/Prelude.hs
ex1.hs
Main>
Obs: Det fullständiga namnet på kommandot är :load så tecknet
efter kolon är bokstaven l, inte siffran 1.
Main> pund(1000) 78.2626 Main> pund(12345) 966.151 Main> pund(127775) 10000.0 Main>
Det är inte alltid det är så lätt. Vi betraktar följande exempel.
En affär säljer potatis för 3.50 kr/kilo. För att stimulera till stora inköp så erbjuds det rabatterade priset 3 kr/kilo för den kvantitet som överstiger 10 kg vid ett och samma inköpstillfälle. Vi vill definiera en funktion som givet en vikt beräknar priset.
Låt oss kalla funktionen price och parametern v.
Om v är högst 10 är det lätt: då blir priset 3.50*v.
Om v är större än 10 blir det litet värre, men man kan
resonera på följande sätt: De första 10 kilona kostar 35 kr och de
sista v-10 kostar 3 kr/kilo, så totalt blir det
35+3*(v-10), vilket kan förenklas till 5+3*v.
Vi har alltså
price(v) = 3.5*v om v<=10
price(v) = 5+3*v om v>10
Hur skriver man detta i Haskell? Först konstaterar vi att man med
hugs kan jämföra tal:
Prelude> 3 < 6 True Prelude> 3.5 <= 3 False Prelude> circleArea(10) > 3*100 TrueResultaten av sådana jämförelser som dessa blir antingen True eller False. Observera att "mindre än eller lika med" måste skrivas <= eftersom den symbol man normalt använder i matematiken inte finns på tangentbordet. Den viktigaste användningen av jämförelser är i funktionsdefinitioner som i följande definition av price:
price(v) |v <= 10 = 3.5*v |v > 10 = 5+3*vNär man laddat in denna definition och använder funktionen på ett visst argument så gör hugs i tur och ordning (uppifrån och ner) jämförelserna till vänster om likhetstecknet tills den finner en jämförelse som blir True. Därefter används motsvarande högerled för att beräkna resultatet. Ladda in och testa funktionen price på några olika argument.
|v > 11 = 5+3*vVad blir nu värdet av funktionen för argumenten 9.5, 10.5 respektive 11.5?
price(v) |v <= 11 = 3.5*v |v > 10 = 5+3*vVad blir nu värdet av funktionen för argumenten 9.5, 10.5 respektive 11.5?
price(v) |v <= 10 = 3.5*v |True = 5+3*vI det sista fallet gör vi ingen jämförelse, utan skriver direkt värdet True.
Vi ska också igen påpeka ett skrivsätt som vi i fortsättningen kommer att använda:
Main> price 7.5 26.25 Main> price 11 38.5 Main> price(4+8) 41.0 Main>Då en funktion används på ett enkelt argument så behöver man inte sätta ut parenteser om argumentet. Däremot behövs ett mellanslag före argumentet. Om däremot argumentet är sammansatt (som 4+8) så måste det omges av parenteser.
mv x y = (x+y)/2
Main> mv 5 8 6.5 Main>
max x y | x >= y = x | otherwise = yDetta är en lösning på föregående övning. Den verkar obekant på två sätt:
otherwise = True
Man tycker alltså bara att det ser trevligare ut att kunna skriva som
ovan och har därför infört ett till namn på True.
Prelude> pi 3.14159 Prelude>
ERROR "ex1.hs" (line 2): Definition of variable "max" clashes with importär det troligt att detta är orsaken (om man inte själv gjort två olika definitioner med samma namn!).
Main> 5 `mv` 4 4.5 Main> 4 `max` (5 `max` 2) 5 Main> (+) 3 4 7 Main>Ett variabelnamn kan användas som en operator (dvs mellan sina argument) om det omsluts av ` (OBS: inte ' som också finns på tangentbordet!). En operator kan skrivas före sina argument om den omsluts av parenteser.
Prelude> div 17 5 3 Prelude> div 34 8 4 Prelude> div 5 9 0 Prelude> 4 `div` 2 2 Prelude>Observera hur vi i det sista exemplet gör en operator av namnet div.
Prelude> 17 `mod` 5 2 Prelude> 34 `mod` 8 2 Prelude> 5 `mod` 9 5 Prelude> mod 4 2 0 Prelude>
För att lösa detta problem behöver ni troligen veta hur man jämför om två tal är lika: det gör man med operatorn == (ja, två likhetstecken!).
Vi betraktar följande lek med tal:
Tänk på ett heltal större än ett. Om talet är jämnt, dela det med två, annars multiplicera det med tre och lägg till ett. Om det nya talet är ett så sluta, annars upprepa förfarandet.Som exempel, låt oss tänka på talet 10. Vi får då följande följd av tal:
Om vi i stället börjar med 7 så får vi följden (kontrollera!)
7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1.
Vi ska intressera oss för frågan: Givet ett tal n, hur många tal blir det i följden? För n=10 blir det 7 tal (10, 5, 16, 8, 4, 2, 1). För n=7 blir det 17 tal (se ovan). Observera att vi räknar med både n själv och den avslutande ettan.
Hur ska vi få hjälp av hugs att lösa detta problem? Vi börjar med att definiera en funktion next som givet ett heltal ger nästa tal i följden.
steps n |n == 1 = 1 |otherwise = steps(next n)+1Det förvånande är att detta faktiskt duger som definition av funktionen. Denna definition är annorlunda än alla vi sett tidigare: Vi använder funktionen själv i högerledet. Pröva att skriva in definitionen, ladda in filen och pröva att beräkna steps n för några olika n. Som ni kommer att upptäcka verkar det hela fungera utmärkt. Men samtidigt kan man undra:
numbers n
|n==1 = [1]
|otherwise = n : numbers(next n)
Laddar man in funktionen (pröva!) så får man:
Main> numbers 10 [10, 5, 16, 8, 4, 2, 1] Main> numbers 17 [17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1] Main> numbers 1 [1] Main>Vi studerar funktionsdefinitionen. Om n=1 så blir resultatet listan [1]. Annars använder vi operatorn : som kan utläsas "före" eller "följt av". Resultatet numbers n är n följt av listan numbers(next n).
När vi har definierat funktionen numbers så behöver vi inte steps eftersom det i preluden finns en funktion length som beräknar längden av en lista:
Main> length [1,6,33,8,7,14] 6 Main> length(numbers 10) 7 Main>Men nu har vi gått alldeles för långt... Det är dags att börja gå igenom språket och dess programmeringsteknik mer systematiskt. Vi börjar imorgon!