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

Funktsionaalprogrammeerimine 2023/24 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
    • Tüübituletus
    • 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)

Kodutöö 12

Selles praktikumis jätkame sõltuvate tüüpidega ning vaatame ka lineaarsete tüüpidega arvutamist.

Lisalugemine (vabatahtlik):

Idris 2: Quantitative Type Theory in Action peatükid:

  • 2: IDRIS 2 AND QUANTITATIVE TYPES
  • 3: RESOURCE USAGE PROTOCOLS

Kui jääte (kodus) hätta, siis vektorite ja kujuliste puude kohta väga hea loengu leiate TalTech-i FP kursuselt, siit.

Sõltuvate tüüpidega programmeerimine -- kujulised puud

Listi pikkust saab käsitleda ka kui selle kuju. Andmestruktuuri kujuks on andmestruktuuri tekitatav vari:

 []        [1]        [1,2]        [1,2,3]
 []        [▒]        [▒,▒]        [▒,▒,▒]
  Z         S Z        S S Z        S S S Z

Listi kujuks on selle pikkus, milleks on naturaalarv. Sarnase mõttekäiguga saab luua ka sõltuva tüübiga puud, mille tüüp sõltub puu kujust.

data TreeShape : Type where
  LeafShape : TreeShape
  NodeShape : (l : TreeShape) -> (r : TreeShape) -> TreeShape

data Tree : TreeShape -> Type -> Type where
  Leaf : Tree LeafShape a
  Node : (left : Tree l a) -> (this : a) -> (right : Tree r a) ->
       Tree (NodeShape l r) a

Eq TreeShape where
    LeafShape == LeafShape  = True
    LeafShape == (NodeShape l r)  = False
    (NodeShape l r) == LeafShape  = False
    (NodeShape l r) == (NodeShape x y)  = 
        l==x && r==y

Eq a => Eq (Tree s a) where
    Leaf == Leaf = True
    (Node left this right) == (Node x y z) = 
        left == x && this == y && right == z

tr1 : Tree (NodeShape LeafShape (NodeShape LeafShape LeafShape)) Int
tr1 = Node Leaf 1 (Node Leaf 2 Leaf)

tr2 : Tree (NodeShape LeafShape (NodeShape LeafShape LeafShape)) Int
tr2 = Node Leaf 3 (Node Leaf 1 Leaf)

Ülesanne 1 - zip_tree

Zip tavaliste puude puhul:

 data Tree : Type -> Type where
   Leaf : Tree a
   Node : Tree a -> a -> Tree a -> Tree a

 zip_tree : Tree a -> Tree b -> Tree (a, b) 
 zip_tree Leaf Leaf  =  Leaf
 zip_tree Leaf (Node left this right)  =  Leaf
 zip_tree (Node left this right) Leaf  =  Leaf
 zip_tree (Node left1 this1 right1) (Node left2 this2  right2)  =
    Node (zip_tree left1 left2) (this1 , this2) (zip_tree right1 right2)

Nüüd tehke sama kujuliste puudega:

zip_tree : Tree shape a -> Tree shape b -> Tree shape (a, b)
zip_tree x y = ?zip_tree_rhs

Näide:

 zip_tree tr1 tr1 == Node Leaf (1, 1) (Node Leaf (2, 2) Leaf)
 zip_tree tr1 tr2 == Node Leaf (1, 3) (Node Leaf (2, 1) Leaf)

Ülesanne 2 - flip_tree

Implementeerige kujuliste puude ümberpööramine

flip_shape : TreeShape -> TreeShape
flip_shape LeafShape = LeafShape
flip_shape (NodeShape l r) = (NodeShape (flip_shape r) (flip_shape l))

flip_tree : Tree shape a -> Tree (flip_shape shape) a
flip_tree = ?flipTree_rhs

Näide:

 flip_tree tr1 == Node (Node Leaf 2 Leaf) 1 Leaf
 flip_tree tr2 == Node (Node Leaf 1 Leaf) 3 Leaf

Ülesanne 3* -- asendamine puudes

Esmalt defineerime tüübi PathTo, millega viidata puude elementidele. PathTo esimene argument on viidatava elemendi kuju; teine argument on kogu puu kuju.

data  PathTo:(targetS:TreeShape) -> (treeS : TreeShape) -> Type  where
    Here  :  PathTo targetS targetS
    Left  :  (path : PathTo targetS l) -> PathTo targetS (NodeShape l r)
    Right :  (path : PathTo targetS r) -> PathTo targetS (NodeShape l r)

Implementeerige puu sees asendamine.

replace_node : a -> PathTo (NodeShape l r) sh -> Tree sh a -> Tree sh a
replace_node new path tree = ?replace_node_rhs

Näide:

 replace_node 9 (Right Here) tr1 == Node Leaf 1 (Node Leaf 9 Leaf)
 replace_node 9 Here tr1 == Node Leaf 9 (Node Leaf 2 Leaf)

Ülesanne 4* -- tree concatenation

Puude kujude konkateneerimine: viidatud lehe asemel pannakse esimese argumendi kuju.

shape_graft  :  (branchS : TreeShape) -> (stalkS : TreeShape) ->
    (path : PathTo LeafShape stalkS) -> TreeShape
shape_graft branchS LeafShape Here  =  branchS
shape_graft branchS (NodeShape l r) (Left path)  =
    NodeShape (shape_graft branchS l path) r
shape_graft branchS (NodeShape l r) (Right path)  =
    NodeShape l (shape_graft branchS r path)

Puude konkateneerimine: viidatud lehe asemel pannakse funkstiooni esimene argument.

tree_graft : (branch : Tree branchS a) -> (stalk : Tree stalkS a) ->
             (path : PathTo LeafShape stalkS) ->
             Tree (shape_graft branchS stalkS path) a
tree_graft branch stalk path = ?tree_graft_rhs

Näide:

 tree_graft tr2 tr1 (Left Here) == Node (Node Leaf 3 (Node Leaf 1 Leaf)) 1 (Node Leaf 2 Leaf)
 tree_graft tr2 tr1 (Right (Right Here)) == Node Leaf 1 (Node Leaf 2 (Node Leaf 3 (Node Leaf 1 Leaf)))

Lineaarsete tüüpidega programmeerimine

Puhta funktsionaalse programmeerimine üks nõrkus on, et vanad väärtused võivad kättesaadavaks jääda. Ning isegi siis, kui vana väärtus ei ole enam kättesaadav, peab automaatne mälukoris esmalt selle fakti tuvastama ja alles siis saab struktuuri mälu vabastada.

Üks lahendus, mis võimaldab vahetulemuste kustutamist mälust, on programmeerimine lineaarsete tüüpidega.

Etteantud kood loengust: lineaarne massiiv

import Data.IOArray
import Data.List

namespace Massiivid 
    %hide Builtin.(#)
    %hide Builtin.DPair.(#)
    infix 5 # 

    public export 
    data L : Type -> Type -> Type where
        (#) : (1 _ : a) -> b -> L a b

    export
    interface Array arr where
      write  : (1 a : arr) -> Nat -> Double -> arr
      read   : (1 a : arr) -> Nat -> L arr Double
      size   : (1 a : arr) -> L arr Nat
      withArray : Nat -> ((1 a : arr) -> L arr c) -> c      


    export 
    Array (List Double) where
        write [] n d = []
        write (x :: xs) 0 d = d :: xs
        write (x :: xs) (S k) d = x :: write xs k d 

        read [] n = [] # 0.0
        read (x :: xs) 0  = (x::xs) # x
        read (x :: xs) (S n) = 
            let _ # r = read xs n in 
            x::xs # r 

        size []        = [] # 0 
        size (x :: xs) = (x :: xs) # (length (x :: xs))

        withArray l f = 
            let _ # r = f (replicate l 0.0) in
            r

    export
    data LinArray = MkLinArray (IOArray Double)

    private
    newIOArr : List Double -> IO (IOArray Double)
    newIOArr xs = 
        let l = cast (length xs) in
        do a <- newArray l
           let upd = zip [0..l-1] xs
           traverse_ (uncurry $ writeArray a) upd
           pure a

    export
    Array LinArray where
        withArray n f = 
            unsafePerformIO (do a <- newArray (cast n)
                                let (_ # r) = f (MkLinArray a) 
                                pure r )

        size (MkLinArray a) = MkLinArray a # (cast (max a))

        write (MkLinArray a) i v = 
            unsafePerformIO (do ok <- writeArray a (cast i) v
                                pure (MkLinArray a))
        read (MkLinArray a) i = 
            unsafePerformIO (do r <- readArray a (cast i)
                                case r of
                                    Just v  => pure (MkLinArray a # v)
                                    Nothing => pure (MkLinArray a # 0))

Ülesanne 5 : implementeeri writeAll

Funktsioonikutse writeAll a xs n kirjutab listi xs elemendid massiivi a alustades indeksist n.

(Võite ette kujutada, et massiiv a on ülisuur ja me ei taha selle vahetulemusi mälus hoida.)

writeAll : Array arr => (1 _ : arr) -> List Double -> Nat -> arr
writeAll a xs n = ?writeAll_rhs

testimine:

    Main> writeAll [1.0,2.0,3.0] [5.0,6.0,7.0] 0
    [5.0, 6.0, 7.0]

Ülesanne 6: massiivi teisendamine listiks

Funktsioonikutse toLst a i j tagastab massiivi a elemendid indeksitega i kuni j listina.

(Võite ette kujutada, et massiiv a on ülisuur ja me ei taha selle vahetulemusi mälus hoida. Aga vahel tahame mingit lõiku listina vaadelda.)

toLst : Array arr => (1 _ : arr) -> Nat -> Nat -> L arr (List Double)
toLst a s f = ?toLst_rhs

test:

 Main> toLst [1.0,2.0,3.0] 0 3
 [1.0, 2.0, 3.0] # [1.0, 2.0, 3.0]

Ülesanne 7: implementeeri massiivis elementide vahetamist

 
swap : Array arr => (1 _ : arr) -> Nat -> Nat -> arr
swap a i j = ?swap_rhs

test:

 Main> swap [1.0,2.0,3.0] 0 2
 [3.0, 2.0, 1.0]

Etteantud kood: quickSort

-- pseudokood:
-- loop a pivot j hi i = 
--   while (j>=hi){
--      if (a[j] < pivot) {
--          swap a[i] a[j]    
--          i++
--      }
--      j++
--   }
--   return i


loop: Array arr => (1 a : arr) -> (pivot:Double) -> (j:Nat) 
                -> (hi:Nat) -> (i:Nat) -> L arr Nat
loop a pivot j hi i =
    if j > hi then a # i else 
        let a # aj = read a j in
        if aj < pivot then 
            let a = Main.swap a i j in
            loop a pivot (j+1) hi (i+1) 
        else
            loop a pivot (j+1) hi i 

-- pseudokood:
-- partition a lo hi = 
--    pivot = a[hi]                  // pivot on viimane element
--    i = loop a pivot lo (hi-1) lo  // tsükkel, kus viimast elementi ei vaata
--    swap a[i] a[hi]                // nüüd vahetame viimase elemendi
--    return i                       // tagastame pivot-i indeksi

-- abiks Nat-i vähenamine
decr : Nat -> Nat 
decr 0 = 0
decr (S n) = n

partition : Array arr => (1 a : arr) -> (lo: Nat) -> (hi: Nat) -> L arr Nat
partition a lo hi = 
    let a # pivot = read a hi 
        a # i     = loop a pivot lo (decr hi) lo
        a         = Main.swap a i hi
    in a # i


-- pseudokood:
--  qs a lo hi = 
--    if (lo<hi) { 
--       pivot_index = partition a lo hi
--       qs a lo (pivot_indeks - 1)
--       qs a (pivot_indeks + 1) hi
--    }
--    return a                  

qs : Array arr => (1 a : arr) -> (lo: Nat) -> (hi: Nat) -> arr
qs a lo hi = 
    if (lo>=hi) then a else 
    let a # pi = partition a lo hi 
        a      = qs a lo (decr pi)
        a      = qs a (1 + pi) hi
    in a 

-- sorteerib kogu massiivi
quickSortArray : Array arr => (1 _ : arr) -> arr
quickSortArray a = 
    let a # len = size a in
    qs a 0 (decr len)

testimine:

 Main> quickSortArray [3.0,2.0,1.0,4.0]
 [1.0, 2.0, 3.0, 4.0]

Ülesanne 7: listi sorteerimine kasutades LinArray-t

quickSortList : List Double -> List Double
quickSortList xs = withArray len f 
        where len : Nat
              len = length xs
              f : (1 _ : LinArray) -> L LinArray (List Double)
              f a = ?f_rhs

testList : List Double
testList = map cast $ concat [[20,18..0],[1,3..19]]

testimine:

 Main> :exec printLn $ quickSortList [3.0,2.0,1.0,4.0]
 [1.0, 2.0, 3.0, 4.0]
 Main> :exec printLn $ quickSortList testList
 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0]

 

  • 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