Arvutiteaduse instituut
  1. Kursused
  2. 2022/23 sügis
  3. Funktsionaalprogrammeerimine (LTAT.03.019)
EN
Logi sisse

Funktsionaalprogrammeerimine 2022/23 sügis

  • Üldinfo
    • Õppekorraldus
  • Loeng
    • Baasväärtused ja tüübid
    • 𝜆-arvutus
    • Kõrgemat järku funktsioonid
    • Interaktiivne programmeerimine
    • Uute tüüpide loomine
    • Liidesed
    • Sisend-Väljund
    • Laiskus
    • Lihtsalt tüübitud 𝜆-arvutus
    • Sõltuvad tüübid
    • Tõestamine Idrises
    • Kvantitatiivne tüübiteooria
  • Praktikum
    • KKK
    • Installimine
    • Kodutöö 1
    • Kodutöö 2
    • Kodutöö 3
    • Kodutöö 4
    • Kodutöö 5
    • Kodutöö 6
    • Kodutöö 7
    • Kodutöö 8
    • Kodutöö 9
    • Kodutöö 10
    • Kodutöö 11
    • Kodutöö 12
    • Kordamine
    • Projektid
  • Moodle
  • Zulip (sisselogides näed linki)

Kordamine: liidesed

Liidesed (Haskellis tüübiklassid) on viis kuidas kasutada ad-hoc polümorfismi s.t. erinevatele tüüpidele erinev implementatsioon.

Meile juba tuttav liides Eq on defineeritud kui:

interface Eq ty where
  (==) : ty -> ty -> Bool
  (/=) : ty -> ty -> Bool

kus Eq on liidese nimi ja ty liidese parameeter.

Siiani nähtud liidesed on näiteks:

Prelude.Eq : Type -> Type
Prelude.Num : Type -> Type
Prelude.Fractional : Type -> Type
Prelude.Show : Type -> Type

aga ka

Prelude.Foldable : (Type -> Type) -> Type
Prelude.Functor : (Type -> Type) -> Type

Liidesed defineerivad tüübile teatud polümorfsed funktsioonid, näiteks:

Prelude.(==) : Eq ty => ty -> ty -> Bool
Prelude.(/=) : Eq ty => ty -> ty -> Bool
Prelude.show : Show ty => ty -> String
Prelude.foldr : Foldable t => (elem -> acc -> acc) -> acc -> t elem -> acc
Prelude.map : Functor f => (a -> b) -> f a -> f b

Liidestel Eq, Num, Fractional, Show on üks parameeter tüübiga Type, kusjuures liidese parameetri nimi on antud sel juhul lühendiga ty. Parameeter ty võib olla ükskõik millist tüüpi. Nende liideste kohta öeldakse, et need on tüübi Type poolt parametriseeritud.

Küll aga saab liidestel olla mitu parameetrit ning nad võivad olla parametriseeritud ka muu tüübi poolt kui Type, näiteks Type -> Type, nagu liidesed Foldable ja Functor. Nende parameetrid t ja f tähistavad parametriseeritud tüüpe, nagu näiteks List või Tree, mis käituvad justkui konteinerid, mis hoiavad mingit tüüpi väärtuseid. Üks meile tuttav parametriseeritud tüüp on ka IO.

Functor, Applicative ja Monad

Huvilistele: hea blogi Funktoristest, Aplikatiivsetest funktoritest ja Monaadidest. See on Haskelli baasil, aga peaks olema arusaadav ka praeguse Idrise baasi pealt. Lihtsalt peab mäletama, et funktsiooni tüübideklaratsioon on Haskellis ::, vastupidiselt Idrisele :.

Prelude.map : Functor f => (a -> b) -> f a -> f b
Prelude.pure : Applicative f => a -> f a
Prelude.<*> : Applicative f => f (a -> b) -> f a -> f b
Prelude.(>>=) : Monad m => m a -> (a -> m b) -> m b

Parameetrid f ja m tähistavad parametriseeritud tüüpe.

Funktor

interface Functor (f : Type -> Type) where
  map : (func : a -> b) -> f a -> f b

Funktorid võimaldavad rakendada funktsiooni geneerilise parametriseeritud tüübi kõigile elementidele. Idrise prelüüdis on defineeritud funktorid kõigile ühe parameetriga (parameetrilistele) tüüpidele, millele on võimalik seda defineerida, näiteks List, Maybe ja IO.

Funktsiooni map kasutades saame võtta misiganes funktori struktuuri ja transformeerida kõik selle funktori elemendid, tagastades uue objekti, mis on täpselt sama struktuuriga, kuid erinevate elementidega. Uued elemendid saavad olla kas sama või hoopis teist tüüpi.

Näiteks:

Idris> map (*2) [1,2,3,4]
[2, 4, 6, 8] : List Integer

Seda ideed saame kasutada mitmete erinevate andmestruktuuridega, kuid see töötab vaid puhaste funktsioonide korral. Kui me tahame rakendada igale elemendile mingit funktsiooni, mis ei ole puhas, s.t. vajab monaadilist efekti, ei saa me kasutada map funktsiooni. Selleks vajame aplikatiivset funktorit.

Applikatiivsed funktorid

interface Functor f => Applicative (f : Type -> Type) where
    pure  : a -> f a
    (<*>) : f (a -> b) -> f a -> f b

Idrise puhtad funktsioonid ei võimalda teha mittepuhtaid arvutusi. Funktsiooniga pure saame konteinerisse väärtuseid panna. Ning, funktsiooniga (<*>) (tuntud ka kui "apply"), kui meil on konteiner funktsiooniga ja konteiner argumendiga, peame saama konteineri funktsiooni tulemusega.

Liides Traverse

Funktsioon traverse map-ib kõik struktuuri elemendid avaldisteks, väärtustab need avaldised ning kombineerib saadud tulemused.

interface (Functor t, Foldable t) => Traversable t where
  traverse : Applicative f => (a -> f b) -> t a -> f (t b)

Funktsioon traverse võtab kaks argumenti:

  1. Funktsioon, mis transformeerib objekti kasutades mõnda efekti (aplikatiivset või monaadilist)
  2. Konteiner nendest objektidest (ja see konteiner on nii funktor kui foldable)

ja tagastab uue konteineri, kus kõigile elementidele on rakendatud transformatsiooni, ning see konteiner on omakorda rakendatud efekti sees.

Intuitsioon: tegemist on for-tsükliga.

readMaybe : IO (Maybe Int)
readMaybe = do
  input <- getLine
  if all isDigit (unpack input)
    then pure (Just (cast input))
    else pure Nothing

loeArv : IO Int
loeArv = do
    putStrLn "Sisesta arv:"
    v <- readMaybe
    case v of 
        Nothing => do
            putStrLn "Viga! Ei tunne sellist arvu!"
            loeArv
        Just n => 
            pure n

summaN2 : IO ()
-- Variant 1 (traverse)
summaN2 = do
    putStr "Liidetavaid. "
    n <- loeArv
    xs <- traverse (const loeArv) [1..n]
    printLn (sum xs)

Liides Sequence

Funktsioon sequence väärtustab kõik avaldised struktuuris ning kogub tulemused kokku.

sequence : Applicative f => Traversable t => t (f a) -> f (t a)
  sequence = traverse id
summaN2 : IO ()
-- Variant 2 (sequence)
summaN2 = do
    putStr "Liidetavaid. "
    n <- loeArv
    xs <- sequence (map (\a => loeArv) [1 .. n])
    printLn (sum xs)

Monaadid

Monaadid võimaldavad teha järjestikulisi toiminguid sõltuvalt eelneva toimingu tulemusest. Intuitsioon: järjestikku täidetavad arvutusmasinad.

Kõik monaadid peavad olema funktorid. S.t. neil on funktsioon map. Lisaks peavad monaadid olema aplikatiivsed funktorid. S.t. neil on ka funktsioonid pure ja (<*>). Ning monaad pole mitte ainult aplikatiivne funktor, vaid tal peab olema ka >>= operaator.

interface Applicative m => Monad m where
     (>>=) : m a -> (a -> m b) -> m b

Ülesanne 1: Kivi-paber-käärid mäng

Implementeeri kivi-paber-käärid mäng arvuti vastu.

Lisa faili algusesse impordid System.Random, Data.Vect, Data.String

Random valiku tegemiseks kasuta funktsiooni rndSelect'.

data Valik = Kivi | Paber | Käärid

valik : Vect 3 Valik
valik = [Kivi, Paber, Käärid]

Show Valik where
    show Kivi = "kivi"
    show Paber = "paber"
    show Käärid = "käärid"

Eq Valik where
    x == y = ?rhs1

loeValik : IO Valik
loeValik = do
    putStr "Sisesta: kivi, paber või käärid: "
    v <- getLine
    case (Data.String.toLower v) of 
        "kivi"   => pure Kivi
        "paber"  => pure Paber
        "käärid" => pure Käärid
        _ => putStr "Ei tea sellist valikut, proovi uuesti. " >> loeValik

kpk : IO ()
kpk = ?rhs2

Näiteks:

K8a> :exec kpk
Sisesta: kivi, paber või käärid: käärid
Viik. Proovi uuesti. Sisesta: kivi, paber või käärid: paber
Sina võitsid paber vs kivi

K8a> :exec kpk
Sisesta: kivi, paber või käärid: käärid
Kaotasid :( käärid vs kivi

Ülesanne 2: Maybe monaad

Olgu antud järgmine avaldispuude andmestruktuur:

data Expr = Const Int | Add Expr Expr | Div Expr Expr

expr1 : Expr
expr1 = Div (Add (Const 3) (Const 1)) (Const 2)
expr2 : Expr
expr2 = Add (Const 1) (Div (Const 1) (Add (Const 1) (Const (-1))))

Kasutada do-notatsiooni, kirjuta funktsioon, mis väärtustab avaldispuu või tagastab Nothing kui selles tekib nulliga jagamine:

evalExpr : Expr -> Maybe Int
evalExpr e = ?rhs_evalExpr

Näiteks:

evalExpr expr1 == Just 2
evalExpr expr2 == Nothing

NB! Jagamiseks kasuta funktsiooni div mitte /.

Küsimused? 8 Kodutöö lahendamine? Vaba kava.

  • Arvutiteaduse instituut
  • Loodus- ja täppisteaduste valdkond
  • Tartu Ülikool
Tehniliste probleemide või küsimuste korral kirjuta:

Kursuse sisu ja korralduslike küsimustega pöörduge kursuse korraldajate poole.
Õppematerjalide varalised autoriõigused kuuluvad Tartu Ülikoolile. Õppematerjalide kasutamine on lubatud autoriõiguse seaduses ettenähtud teose vaba kasutamise eesmärkidel ja tingimustel. Õppematerjalide kasutamisel on kasutaja kohustatud viitama õppematerjalide autorile.
Õppematerjalide kasutamine muudel eesmärkidel on lubatud ainult Tartu Ülikooli eelneval kirjalikul nõusolekul.
Courses’i keskkonna kasutustingimused