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 ()
```