* Huom* Tehtävistä saa esittää kritiikkiä. Tällä kertaa näitä on myös paljon, mutta varmaankin käytämme jälleen kaksi viikkoa näiden miettimiseen.
Yksi helpoimmin ymmärrettävä ja samalla hyvä esimerkki monadien käytöstä on merkkijonon tulkinta. Siispä leikimme jälleen olevamme rakentamassa pätevää ohjelmaa suorakaiteiden käsittelyyn. Tällä kertaa saamme kuitenkin tehtäväksemme lukea suorakaiteita koskevia lausekkeita tekstitiedostosta.
Tiedostossa voi olla joko suorakaide literaaleja, kuten <100,200-10+10>
mikä tarkoittaisi suorakaidetta, jonka oikea ylänurkka on pisteessä (–10,10) ja jonka leveys on 100 ja korkeus 200 yksikköä. Lisäksi tiedostossa on laskutoimituksia, kuten esimerkiksi:
<10,10+0+0> `yhdiste` (<10,10+5+5> `leikkaus` <10,10+0+5>)
(<10,10+5+5> `leikkaus` <10,10+0+5>) `leikkaus` (<10,10+8+5> `leikkaus` <20,10+0+5>)
Lisää esimerkkejä löydät rectangles-tiedostosta
Kokeile noin 15–20min luoda sopivaa funktiota, joka lukee tekstitiedoston ja palauttaa siinä määritellyt suorakulmiot.
Määrittele sopiva tyyppi kuvaamaan lausekkeita. Käytä hyväksesi edellisten demojen tulosta. Esimerkiksi:
data Lauseke = Suorakaide (Rectangle)
| Yhdiste Lauseke Lauseke
| Leikkaus Lauseke Lauseke
on hyvä lähtökohta.
Perustyyppimme on String -> Maybe (a, String)
, eli funktio joka ottaa merkkijonon ja yrittää lukea siitä jonkun arvon a. Jos lukeminen onnistuu palautetaan arvo ja jäljelle jäänyt merkkijono. Jos lukeminen ei onnistu, palautetaan Nothing
Määrittele
Jäsennin a = J (String -> Maybe (a,String)
.merkki :: Char -> Jäsennin Char
, joka lukee annetun kirjaimen tai epäonnistuu.tyhjä :: Jäsennin ()
, joka lukee yhden tyhjän merkin, kuten välilyönnin tai epäonnistuu.aakkonen :: Jäsennin Char
, joka lukee yhden aakkosen tai epäonnistuu.numeroMerkki :: Jäsennin Char
, joka lukee yhden numeromerkin tai epäonnistuu.failure :: Jäsennin ()
, joka epäonnistuu aina.return :: a -> Jäsennin a
, joka onnistuu aina vakioarvolla a
.ajaJäsennin :: Jäsennin a -> String -> Maybe (a,String)
Jäsentimen olisi hyvä olla funktori, sillä tällöin voisimme käyttää monia valmiita funktioita. Esimerkiksi voisimme määritellä jäsentimen, joka lukee numeron ja palauttaa sen kokonaislukuna:
numero :: Jäsennin Int
numero = fmap (read . box) numeroMerkki
box x = [x]
Functor
instanssi jäsentimelleApplikatiivinen funktori olisi myös hyödyllinen määritelmä jäsentimelle, sillä jos jäsennin olisi applikatiivinen funktori, niin voisimme kirjoittaa esimerkiksi seuraavat näppärät määritelmät:
sulutettu a = merkki '(' *> a <* merkki ')'
pari a = (,) <$> a <*> a
kolmikko a = (,,) <$> a <*> a <*> a
(*>)
ja (<*)
tekevät?Applicative
-instanssi jäsentimelle.Tyyppiluokka Alternative
laajentaa Applicative
tyyppiluokkaa operaatiolla <|>
, joka esittää vaihtoehtoisuutta. Jos jäsentimemme olisi Alternative
voisimme kirjoittaa:
etumerkki = merkki '+' <|> merkki '-'
Alternative
-instanssi.Alternative
-luokalle määritellyt funktiot some
ja many
tekevät, ja miten ne on oletusarvoisesti määritelty.tyhjää :: Jäsennin ()
, joka lukee kaikki tyhjät merkit, mutta ei epäonnistu vaikka niitä ei olisikaan.sana :: Jäsennin String
.luku :: Jäsennin Int
.Monoidi on joukko, jolla on yksikköalkio ja assosiatiivinen operaatio. Esimerkiksi kokonaisluvut, nolla ja yhteenlasku muodostavat monoidin. Myös parserit muodostavat monoidin.
Monoid
-instanssi.Jäsentimet ovat varmaan myös monadeja, sillä on helppo keksiä tarve bind
operaatiolle:
kaksiSamaaKirjainta = bind merkki $ aakkonen
Lisäksi, monadeilla on hyviä yleiskäyttöisiä operaatioita, kuten mapM
ja replicateM
, sekä niitä on helppo ketjuttaa peräkkäin (>>=)
ja >>
operaattoreilla.
vakioSana = mapM_ merkki
kolmeKirjainta = replicateM 3 aakkonen
etumerkillinenLuku = (plus <|> miinus) <*> numero
where
plus = merkki '+' >> return id
miinus = merkki '-' >> return negate
bindJäsennin :: (a -> Jäsennin b)
->
(Jäsennin a -> Jäsennin b)
Monad
-instanssi.Arbitrary
-instanssi lauseketyypillesi.Show
-instanssi lausekkeille.quickCheck
-invariantti jäsentimellesi.suorakaide :: Jäsennin Suorakaide
, joka lukee yhden suorakaiteenlauseke :: Jäsennin Lauseke
, joka lukee suorakaiteita koskevan lausekkeen.HUOM: Jos tähän meinaa kulua yli 100 riviä koodia, mieti uudelleen miten eo. instansseja voi hyödyntää!
blog comments powered by Disqus