3. Programmering med textfiler Aarne Ranta Datorintroduktion 2009, D och DV, Chalmers & GU %!target:html %!postproc(html): #NEW %!postproc(html): #HR
#NEW ==En ny titt på kurswebbsidan== [Öppna den här länken rdator.html] Vad har hänt? Och vad har hänt [här sdator.html]? #NEW ==Strängoperationer på en fil== 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 ``` #NEW ==In- och utmatning== IO, programmets kommunikation med det övriga datorsystemet. Exempel: - läsa en fil - skriva en fil - läsa tangenttryck - skriva på skärmen 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.) #NEW ==Preludfunktionen interact== På den här kursen behöver vi ingen annan IO-funktion än ``` interact :: (String -> String) -> IO () ``` Den här funktionen + läser en sträng från input + applicerar en strängfunktion + skriver resultatet till output Strängfunktionen kan vara vilken som helst av typen ``String -> String``, t.ex. alla de som definieras i Föreläsning 2. #NEW ==Hur man skickar indata== 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: - mata in resultatet av ett kommando till det nästa kommandot Om ingen pipe följer skrivs resultatet på skärmen. #NEW ==Lite upprepning av unix-kommandon== ``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 - flera filer - resultat från en pipe 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 ``` #NEW ==Strömmarna stdin och stdout== 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: - det kan få sitt input från ett annat Unix-kommando - det kan skicka sitt output till ett annat Unix-kommando, eller till skärmen #NEW ==Transformera en fil== Vi ska börja med att använda kommandot ``` unix% cat ett.txt | runghc Translate.hs ``` där [``ett.txt`` 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 http://www.lysator.liu.se/runeberg/rodarum/]. 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] ``` #NEW ==En "sammanfattning" av filen== 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 ``` #NEW ==Andra filtransformationer== 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. #NEW ==Högre ordningens funktioner== 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) #NEW ==Generalisering från ord-för-ord översättning== 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 ``` #NEW ==Rad-för-rad översättning== 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? #NEW ==Översättning av ett HTML-dokument== 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: ``` Obs ``` har taggarna ```` och ````. 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`` Translate.hs]. Till exempel: ``` translate = inHTML (byWords reverse) ``` har skapat [den här filen rdator.html]. #NEW ==Output till en fil== 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 ``` #NEW ==Uppgifter== Alla uppgifterna ska implementeras som variationer av ``translate`` i [``Translate.hs`` 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 dator-03.html] 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`` ett.txt]. #NEW ==Sammanfattning och referens== ===Unix-kommandon=== ``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 ===Prelude-funktioner=== ``` main :: IO () -- måste definieras i varje Main-module interact :: (String -> String) -> IO () ```