Vad har hänt?
Och vad har hänt här?
Det enklaste sättet att tillämpa en strängfunktion, t.ex. reverse
på en fil:
1. Skriv filen Translate.hs
med följande innehåll:
module Main where main = interact reverse
2. Kör programmet i en Unix-pipe:
unix$ cat myfile | runghc Translate
IO, programmets kommunikation med det övriga datorsystemet.
Exempel:
Typen av main
är en IO-typ:
main :: IO ()
I exakta termer: IO-aktion som returnerar värde av typ ()
(Typen ()
är Haskells motsvarighet till void
i C och Java.)
På den här kursen behöver vi ingen annan IO-funktion än
interact :: (String -> String) -> IO ()
Den här funktionen
Strängfunktionen kan vara vilken som helst av
typen String -> String
, t.ex. alla de som
definieras i Föreläsning 2.
Från en fil, med unix-kommandot cat
unix$ cat myfile.txt | runghc Translate.hs
Som en sträng, med unix-kommandot echo
unix$ echo "hello world" | runghc Translate.hs
I båda fallen använder man en pipe |
, som (som vid det här laget bekant) betyder:
Om ingen pipe följer skrivs resultatet på skärmen.
echo
STRING: skriv ut STRING
cat
FILE: skriv innehållet av FILE
grep
STRING FILE: skriv ut de rader i FILE som innehåller STRING
wc
FILE: skriv ut antalet rader, ord och tecken i FILE
I stället för FILE, kan man i dessa kommandon använda
Exempel: räkna på hur många rader ordet "Haskell" förekommer på den här kursens hittills 3 föreläsningar
unix$ cat dator-01.txt dator-02.txt dator-03.txt | grep "Haskell" | wc 22 146 1071
Ett (typiskt) unixprogram är ett filter som förvandlar input till output.
Input och output är teckenströmmar, och kan i Haskell ses som listor av tecken. De är, konceptuellt, oändliga.
stdin = standard input, stdout = standard input
För att skriva ett eget program som passar in i den här modellen,
är det tillräckligt att skriva en Haskell-fil, säg Translate.hs
,
i formatet
module Main where main = interact translate
(med en egen translate
-funktion) och sedan exekvera programmet i Unix:
unix% runghc Translate.hs
Nu är detta anrop av runghc
precis ett filter i Unix-bemärkelsen:
Vi ska börja med att använda kommandot
unix% cat ett.txt | runghc Translate.hs
där ett.txt
är det första avsnitten från första kapitlet
av August Strindbergs Röda rummet (1879).
Hela boken finns att hämta från Projekt Runeberg.
Vi strukturerar filen så att vi lätt kan definiera nya översättningsfunktioner:
module Main main :: IO () main = interact translate translate :: String -> String -- translate = reverse translate = initials initials :: String -> String initials s = unwords [take 1 w | w <- words s]
Första bokstaven ur varje ord:
unix$ cat ett.txt | runghc Translate.hs F K S i f D v e a i b a m D l t p M h ä i b ö f a o r v e u s h a s u g f l o h j p a s s k v f a l p å d ö s v t s u e o p s v p s v f a f g i b m l b ä k i s o k å b s b b s l b m s o g ä h i m t s s s v s g b o d l e o l d a b d o b G h p a s u s s d s g u t p n h d d o s a r f s h d p h f u t s å f s u s p R - o a s d D h b i b o k m s p e b d f h e h s i s d s J i f D v e l o e k
Prova också, i tur och ordning:
reverse
, take 2
, reverse
på varje ord separat,
ta bort mellanslag,...
module Main main :: IO () main = interact translate translate :: String -> String -- translate = initials translate = reverse -- translate s = unwords [take 2 w | w <- words s] -- translate s = unwords [reverse w | w <- words s] -- translate s = [c | c <- s, c /= ' ']
Vi gör detta genom att kommentera bort alla definitioner av
translate
förutom en i taget.
En högre ordningens funktion är en funktion som tar en funktion som argument.
Vårt första exempel har varit:
interact :: (String -> String) -> IO ()
Högre ordningens funktioner är en specialitet av Haskell och andra funtionella språk; de är knappast möjliga i Java, och endast med en viss möda i C och C++.
Även i skolmatematik har man högre ordningens funktioner, t.ex. derivatan:
D : (R -> R) -> (R -> R) (funktionen måste vara deriverbar)
Vi har skrivit flera gånger uttryck i samma format:
unwords [reverse w | w <- words s] unwords [take 2 w | w <- words s] unwords [replace w | w <- words s]
Kan vi inte generalisera från detta? Jo - med en högre ordningens funktion:
byWords :: (String -> String) -> String -> String byWords f s = unwords [f w | w <- words s]
Nu kan vi skriva samma uttryck så här
byWords reverse byWords (take 2) byWords replace
Föga oväntat:
byLines :: (String -> String) -> String -> String byLines f s = unlines [f w | w <- lines s]
Jämför följande:
main = interact translate -- translate = reverse -- translate = byWords reverse -- translate = byLines reverse
Vad händer i de olika fallen?
HTML = HyperText Markup Language
Ett HTML-dokument består av text och taggar. Taggarna
ger länkar till andra dokument och säger hur texten ska se ut.
Taggarna är allt innehåll mellan <
och >
; allt annat
är text. Exempel:
<b>Obs</b>
har taggarna <b>
och </b>
. Taggarna ger
fetstil ("boldface") till ordet "Obs", vilket syns i webbläsaren som
Obs.
När vi översätter ett HTML-dokument vill vi oftast lämna taggarna som de är, för att bevara utseendet och länkarna. Vi kan göra detta med funktionen
inHTML :: (String -> String) -> String -> String
som definieras i Translate.hs
. Till exempel:
translate = inHTML (byWords reverse)
har skapat den här filen.
För att se den nya HTML-filen på en webbläsare, måste vi spara den
i en fil. Unix erbjuder operationen omdirektion (>
) för detta:
unix% COMMAND > FILE
skickar output från COMMAND till FILE, i stället för skärmen (eller ett annat kommando).
Vi använder detta för att spara översättningsresultatet:
unix% cat dator-01.html | runghc Translate.hs > new-dator-01.html
Nu kan vi öppna den nya filen i webbläsaren.
Obs. Skriv inte till samma fil!
Varför? Kopiera någon fil till file
och kör
unix% cat file >file
Alla uppgifterna ska implementeras som variationer av translate
i Translate.hs
.
1. Kör några egna översättningar på några egna filer.
2. Definiera en översättning som upprepar den första bokstaven i varje ord i ett HTML-dokument. Texten Kör några egna översättningar blir således KKör nnågra eegna ööversättningar. HTML-taggarna ska bevaras som de är. Redovisning: kör kommandot på denna föreläsning och visa på en webbläsare.
3. Definiera en översättning som gör samma som unix-kommandot wc
:
returnerar antalet rader, ord och tecken. Jämför resultatet med
det som unix-kommandot ger. Obs. gör detta med Preludfunktionerna
även om det blir långsamt. Redovisning: kör kommandot på
ett.txt
.
echo
STRING: skriv ut STRING
cat
FILE: skriv innehållet av FILE
grep
STRING FILE: skriv ut de rader i FILE som innehåller STRING
wc
FILE: skriv ut antalet rader, ord och tecken i FILE
man
COMMAND: visa manualsidan av COMMAND
runghc
FILE: kör main
-funktionen i Haskell-filen FILE
COMMAND1 |
COMMAND2: skicka output från COMMAND1 till input av COMMAND2
COMMAND >
FILE: skicka output från COMMAND till FILE
main :: IO () -- måste definieras i varje Main-module interact :: (String -> String) -> IO ()