Kodutöö 14 (vabatahtlik)
Selles praktikumis vaatame 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
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
module K14 import Data.IOArray import Data.List import System.Random %hide Builtin.infixr.(#) %hide Builtin.DPair.(#) %hide Builtin.swap %hide Data.List.partition infix 7 # data L : Type -> Type -> Type where (#) : (1 _ : a) -> b -> L a b -- Konstrueerimine: lineaarset resrurssi saab panna esimeseks -- komponendiks, aga siis on terve paar lineaarne. -- Mustrisobitusel: kui kogu paar peab olema lineaarne, siis -- selle esimene komponent on lineaarne. (Show a, Show b) => Show (L a b) where show (x # y) = show x ++ " # " ++ show y (Eq a, Eq b) => Eq (L a b) where (x#y) == (z#w) = x==z && y==w -- Lihtsustus: Double-te massiivid (indekseeritud Int-dega) export interface Array arr where write : (1 a : arr) -> Int -> Double -> arr read : (1 a : arr) -> Int -> L arr Double size : (1 a : arr) -> L arr Int withArray : Int -> ((1 a : arr) -> L arr c) -> c export Array (List Double, Int) where write (xs, n) i y = (writeList xs i y, n) where writeList : List Double -> Int -> Double -> List Double writeList [] k y = replicate (cast k) 0 ++ [y] writeList (_ :: xs) 0 y = y :: xs writeList (x :: xs) k y = x :: writeList xs (k-1) y read (x, n) i = (x, n) # readList x i where readList : List Double -> Int -> Double readList [] k = 0 readList (x :: _) 0 = x readList (_ :: xs) k = readList xs (k-1) size (xs, n) = (xs, n) # n withArray n f = let _ # x = f ([], n) in x export data LinArray = MkLinArray (IOArray Double) export Array (LinArray, Int) where withArray n f = unsafePerformIO (do a <- newArray (cast n) let (_ # r) = f (MkLinArray a, n) pure r ) size (MkLinArray a, n) = (MkLinArray a, n) # n write (MkLinArray a, n) i v = unsafePerformIO (do ok <- writeArray a (cast i) v pure (MkLinArray a, n)) read (MkLinArray a, n) i = unsafePerformIO (do r <- readArray a (cast i) case r of Just v => pure ((MkLinArray a, n) # v) Nothing => pure ((MkLinArray a, n) # 0)) withLinArray : Int -> ((1 _ : (LinArray, Int)) -> L (LinArray, Int) c) -> c withLinArray = withArray withListArray : Int -> ((1 _ : (List Double, Int)) -> L (List Double, Int) c) -> c withListArray = withArray toList : Array arr => (1 src: arr) -> L arr (List Double) toList a = let a # n = size a in h (n-1) a [] where h : Int -> (1 src: arr) -> List Double -> L arr (List Double) h j a acc = if j<0 then a # acc else let a # x = read a j in h (j-1) a (x::acc) copyList : Array arr => List Double -> (1 src: arr) -> arr copyList xs a = h 0 xs a where h : Int -> List Double -> (1 src : arr) -> arr h i [] a = a h i (x :: xs) a = h (i+1) xs (write a i x) minDouble : Double minDouble = -1.7976931348623157e+308 maxDouble : Double maxDouble = 1.7976931348623157e+308 test : (List Double, Int) test = ([10.0,11.0,12.0,13.0,14.0],5)
Pane tähele, et massiivide jaoks on antud kaks definitsiooni. Testimiseks listid ja reaalsema näitena IO-massiivid.
Ülesanne 1: implementeeri massiivis elementide vahetamine
Lineaarne funktsioon vahetab massiivis vastavad indeksid ja tagastab massiivi.
swap : Array arr => Int -> Int -> (1 _ : arr) -> arr swap i j a = ?swap_rhs
test:
K14> K14.swap 2 4 test ([10.0, 11.0, 14.0, 13.0, 12.0], 5) K14> K14.swap 0 4 test ([14.0, 11.0, 12.0, 13.0, 10.0], 5)
Ülesanne 2: implementeeri massiivi elementide summeerimine
Lineaarne funktsioon sumArray n a
liidab massiivis a
elemendid indeksitega 0
kuni n
(kaasa arvatud). Lahenda lihtrekursiooniga, et igal rekursiivsel kutsel n
väheneb ühe võrra.
sumArray : Array arr => Int -> (1 _ : arr) -> L arr Double sumArray n a = ?sumArray_rhs
test:
K14> sumArray 0 test ([10.0, 11.0, 12.0, 13.0, 14.0], 5) # 10.0 K14> sumArray (-1) test ([10.0, 11.0, 12.0, 13.0, 14.0], 5) # 0.0 K14> sumArray 4 test ([10.0, 11.0, 12.0, 13.0, 14.0], 5) # 60.0
Testi summeerimist ka päris massiividel
withLinTestArray : (((1 _ : (LinArray, Int)) -> L (LinArray, Int) c)) -> c withLinTestArray f = withLinArray (snd test) (\a => f (copyList (fst test) a))
Test:
K14> :exec printLn (withLinTestArray (sumArray 0)) 10.0 K14> :exec printLn (withLinTestArray (sumArray 4)) 60.0
Ülesanne 3: implementeeri massiivi ümberpööramine
Lineaarne funktsioon rev a
tagastab massiivi, mille elemendid on esialgsega võrreldes vastupidises järjekorras.
rev : Array arr => (1 _ : arr) -> arr rev a = let a # n = size a in f 0 (n-1) a where f : Int -> Int -> (1 _ : arr) -> arr f i j a = ?f_rhs
test:
K14> rev test ([14.0, 13.0, 12.0, 11.0, 10.0], 5)
Testi ümberpöötamist ka päris massiividel
withLinTestArray' : (((1 _ : (LinArray, Int)) -> (LinArray, Int))) -> List Double withLinTestArray' f = withLinArray (snd test) (\a => toList (f (copyList (fst test) a)))
test:
K14> :exec printLn (withLinTestArray' rev) [14.0, 13.0, 12.0, 11.0, 10.0]
Ülesanne 4: implementeeri massiivi map
operatsioon
Lineaarne funktsioon map f a
tagastab massiivi, mille elemendidele on rakendatud funktsioon f
.
mapArray : Array arr => (Double -> Double) -> (1 _ : arr) -> arr mapArray f a = let a # n = size a in g (n-1) a where g : Int -> (1 _ : arr) -> arr g n a = ?g_rhs
test:
K14> mapArray (+100) test ([110.0, 111.0, 112.0, 113.0, 114.0], 5)
Ülesanne 5: implementeeri massiivi fold
operatsioon
foldArray : Array arr => (acc -> Double -> acc) -> acc -> (1 _ : arr) -> L arr acc foldArray f x a = let a # n = size a in h (n-1) a where h : Int -> (1 _ : arr) -> L arr acc h n a = ?h_rhs
test:
K14> foldArray max minDouble test ([10.0, 11.0, 12.0, 13.0, 14.0], 5) # 14.0 K14> foldArray min maxDouble test ([10.0, 11.0, 12.0, 13.0, 14.0], 5) # 10.0 K14> :exec printLn (withLinTestArray (foldArray min maxDouble)) 10.0
Ülesanne 6: implementeeri massiivi normaliseerimine
Normaliseerimise all mõtleme siin operatsiooni, mis leiab massiivi vähima elemendi v
, maksimaalse elemendi s
. Operatsioon lahutab kõigist teistest elementidest v
ja jagab tulemuse s-v
-ga. Seega on tulemuses tavajuhul vähima elemendi indeksil salvestatud null ja maksimaalse elemendi juures 1
. Kõik teised elemendid on positiivsed ja vahemikus 0
kuni 1
. Lisaks tagastada ka v
ja s
.
normalize : Array arr => (1 _ : arr) -> L arr (Double,Double) normalize a = ?normalize_rhs
test:
K14> normalize test ([0.0, 0.25, 0.5, 0.75, 1.0], 5) # (10.0, 14.0)