{- Функции от по-висок ред -} module Exercise9 where {- Ще започнем с позната задача. Искаме да поддържаме списък от книгите, които сме дали назаем на познати -} type Person = String type Book = String type Database = [(Person, Book)] -- Примерен списък mylist :: Database mylist = [("Ivan", "The Godfather"), ("Kalin", "Da Vinci's code"), ("Iliyana", "Fahrenheit 451"), ("Ivan", "The Craft of Functional Programming")] -- Задача 1. Да се намерят всички книги в списъка allBooks :: Database -> [Book] allBooks db = [ b | (p,b) <- db ] -- Задача 2. Да се намерят всички книги заети на някого borrowedTo :: Person -> Database -> [Book] borrowedTo x db = [ b | (p,b) <- db, p == x ] {- Задача 3. Да се запише, че сме дали назаем книга на някого. Ако книгата вече е дадена назаем, да не се прави нищо -} borrow :: Person -> Book -> Database -> Database borrow p b db -- ако книгата вече е заета - не правим нищо | elem b (allBooks db) = db -- ако е свободна, записваме на кого ще я дадем | otherwise = (p,b):db {- Задача 4. Да се отбележи, че някой ни е върнал дадена книга, като изтрием съответния запис от списъка. -} returnBook :: Person -> Book -> Database -> Database returnBook p b db = [ x | x <- db, x /= (p,b) ] {- Функции от по-висок ред. Изобразяване (map) map :: (a -> b) -> [a] -> [b] map f l = [ f x | x <- l ] Примери: -} square x = x * x -- за да получим списък от квадратите на числата в l: -- приложи "на квадрат" над списъка l squares1 l = map square l -- можем да "съкратим" последния аргумент squares2 = map square -- и да използваме анонимна функция вместо square squares3 = map (\x -> x*x) -- още примери: -- списък от последните цифри на числата в l lastdigit x = x `mod` 10 -- първи опит lastdigits1 l = map lastdigit l -- а сега съкращаваме l lastdigits2 = map lastdigit -- а сега с анонимна функция lastdigits3 = map (\x -> x `mod` 10) -- а сега със "секция на оператор" lastdigits4 = map (`mod` 10) -- списък от дължините на стринговете в l lengths1 l = map length l lengths2 l = map length {- А сега да решим Задача 1 с map -} allBooks1 db = [ b | (p,b) <- db ] -- да приведем във вид, удобен за map takeBook (p,b) = b allBooks2 db = [ takeBook (p,b) | (p,b) <- db ] -- хайде сега без двойки allBooks3 db = [ takeBook x | x <- db ] -- готови сме за map allBooks4 db = map takeBook db -- да "съкратим" db allBooks5 = map takeBook -- а сега с анонимна функция allBooks6 = map (\(p,b) -> b) -- човекът не ни трябва - да сложим анонимен шаблон _ allBooks7 = map (\(_,b) -> b) -- последното се чете като - "да намерим всички книги значи от целия списък -- да вземем само книгите {- Филтриране (filter) filter :: (a -> Bool) -> [a] -> [a] filter pred l = [ x | x <- l, pred x ] pred се чете като "предикат" или "условие" Примери: -} -- Всички четни числа от l evens1 l = filter even l -- съкращаваме evens2 = filter even -- всички низове с дължина до 5 isShort s = length s <= 5 shorts1 l = filter isShort l -- съкращаваме shorts2 = filter isShort -- с анонимна функция shorts3 = filter (\s -> length s <= 5) {- Задача 4 с filter -} returnBook1 p b db = [ x | x <- db, x /= (p,b) ] -- сега с помощна функция notThesePersonAndBook (p,b) x = x /= (p,b) returnBook2 p b db = [ x | x <- db, notThesePersonAndBook (p,b) x] -- сега с filter returnBook3 p b db = filter (notThesePersonAndBook (p,b)) db -- съкращаваме db returnBook4 p b = filter (notThesePersonAndBook (p,b)) -- с анонимна функция returnBook5 p b = filter (\x -> x /= (p,b)) -- със "секция на оператор" returnBook6 p b = filter (/=(p,b)) {- А сега да решим задача 2 с filter. Всички книги, заети от някого означава да филтрираме списъка само за дадения човек и след това да вземем само книгите. -} borrowedTo1 x db = [ b | (p,b) <- db, p == x ] -- да разделим на две части: филтриране и вземане на всички книги borrowedTo2 x db = allBooks [ (p,b) | (p,b) <- db, p == x ] -- да приведем във вид удобен за filter -- правим функция isThisPerson, която проверява дали даден запис в списъка -- се отнася за човека X isThisPerson x (p,b) = p == x borrowedTo3 x db = allBooks [ y | y <- db, isThisPerson x y ] -- а сега с filter, където pred = isThisPerson x borrowedTo4 x db = allBooks (filter (isThisPerson x) db) {- ВНИМАНИЕ: не можем да съкратим db! Композиция на функции: (f . g) x = f (g x) Точката се чете като "след", напр. прилагаме f след g Примери -} -- синус на квадрат sinsquared = square . sin -- синус от квадрата sinofsquare = sin . square -- Аналогични дефиниции, но без композиция sinsquared2 x = square (sin x) sinofsquare2 x = sin (square x) -- още примери -- последната цифра на квадрат lastdigitsquared = square . (`mod` 10) -- намислете си едно число. Добавете 7. Умножете по 2. Извадете резултата от 14. Какво получихте? mystery = (14 -) . (* 2) . (+ 7) {- Композицията позволява да прилагаме последователно функции от вида a -> b, b -> c, c -> d. Така можем вече да "съкратим" db в Задача 2 -} borrowedTo5 x = allBooks . filter (isThisPerson x) -- и отново, с анонимна функция borrowedTo6 x = allBooks . filter ((\x (p,b) -> p == x) x) -- можем да "съкратим" \x със прилагането на x borrowedTo7 x = allBooks . filter (\(p,b) -> p == x) -- и накрая цялата задача само с map и filter borrowedTo8 x = map (\(_,b) -> b) . filter (\(p,_) -> p == x) {- Ако приложим функциите borrow и returnBook частично само над два аргумента ще получим функции от тип Database -> Database, които можем да тълкуваме като "операции" над базата данни -} type Operation = Database -> Database operationKalinBorrowsTwilight :: Operation operationKalinBorrowsTwilight = borrow "Kalin" "Twilight" operationIvanReturnsGodfather :: Operation operationIvanReturnsGodfather = returnBook "Ivan" "The Godfather" -- можем да обединим двете операции в едно с композиция operationBorrowAndReturn :: Operation operationBorrowAndReturn = borrow "Kalin" "Twilight" . returnBook "Ivan" "The Godfather" -- да видим какво става със списъка след двойната операция mylistAfterDoubleOperation = operationBorrowAndReturn mylist -- така можем да пазим цялата история на всички заемания. Например: borrowingHistory :: [Operation] borrowingHistory = [ returnBook "Ivan" "The Godfather", borrow "Kalin" "Twilight", borrow "Iliyana" "History of Inventions", borrow "Georgi" "The Alchemist", returnBook "Kalin" "Twilight" ] {- и най-накрая можем да напишем функция, която изпълнява всички операции в историята последователно, като ги слива в една голяма операция -} allOperations :: [Operation] -> Operation allOperations [op] = op allOperations (op:ops) = allOperations ops . op {- забележете, че първо искаме да изпълним op, а след това всичко останало затова op трябва да стои от дясната страна на точката Накрая да видим резултата -} myListAfterAllOperations = allOperations borrowingHistory mylist