Георги Наков, nakov.gl at gmail com
Марин Маринов, marinov.ms+tues at gmail com
Технологично училище "Електронни Системи"
18 Януари 2017г.
Основна цел на Haskell е програмирането чрез генерични, преизползваеми компоненти. Тези компоненти се използват в "поточна линия" - поредица от трансофрмации на дадена задачата към нейното решение. Този стил на програмиране изисква "слепващи средства", които да осигурят потока на данни между компонентите, да спестят на програмиста писане на "глупав" повтарящ се код и да направят крайния резултат синтактично и визуално приятен.
Откриване на броя редове, които се съдържат Curry
В bash
:
cat composition-currying.md | grep Curry | wc -l
В haskell
curryLns :: String -> Int
curryLns = length . filter (contains "Curry") . lines
За преброяването на редовете съдържащи "Curry" използвахме прост алгоритъм:
- разделихме входа на редове
- оставихме само редовете, които съдържат низът "Curry"
- преброихме колко редове са останали
Принципи на Unix приложенията:
- правят едно нещо, но го правят добре
- по-сложните програми са композиция на по-прости
- ново-добавените средства се интегрират лесно със съществуващите посредством общ интерфейс - файлове
- това позволява лесна работа, гъвкавост и продуктивност
- В Unix взаимоработата на две приложения става чрез писане и четене на файлове.
bash
,sh
и дригите shells предоставя удобен начин за пренасочване наstdin
иstdout
посредством|
.|
"свързва" стандарния изход на предходната програма със стандартния вход на последващата, ефективно създавайки еднопосочен канал за предаване на информация.
- Haskell предоставя два основни механизма се слепване - currying и function composition.
- тези механизми спомагат за синатктично лесно и визуално приятно преизползване на готови функци
- това е основната причина защо Haskell програмите обикновено са по-генерични от анаглогична програма в друг език
- В Haskell всяка функция има само 1 параметър
- функциите на много параметри са syntactic sugar за вложени функции с по един парамеър
add :: Int -> Int -> Int
add x y = x + y
addLam :: Int -> Int -> Int
addLam = \x -> \y -> x + y
add :: Int -> Int
add x y = x + y
addLam :: Int -> Int
addLam = \x -> \y -> x + y
> add 4 5
-- (add 4) 5 <- add' y = 4 + y
-- add' 5 <- res = 4 + 5
9
> addLam 4 5
-- (addLam 4) 5 <- addLam' y = \y -> 4 + y
-- addLam' 5 <- res = 4 + 5
9
Автоматичният Currying е причината сигнатурите да изглеждат по начина по който ги познаваме. Всичко това са били функциии!!
Всяко предаване на параметър ни връща нова функция. Това е и причината да трябва да ограждаме функционалните типове в скоби - казваме "хей, искам цялото това да е 1 параметър".
add3 :: Int -> (Int -> (Int -> Int)) -- default
add3 x y z = x + y + z
add2 :: Int -> Int -> Int
add2 = add3 0 -- same as \y -> \z -> 0 + y + z
apply :: (a -> b) -> a -> b
apply f a = f a
Композицията е |
във функционалните езици. Целта му е слепване на изхода на една фунцкия с входа на друга.
Ако имаме функциите:
g :: Int -> Double
g = intToDouble
f :: Double -> String
f = show
То композицията им е функцията:
c :: Int -> String
c x = f (g x)
Haskell Prelude предоставя оператор за композиция.
(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \x -> f (g x)
test x = show (length x)
test' x = (show . length) x
Важно: В Haskell (както и във физиката и математиката) композицията е обратна на тази в bash
. Изразът се чете от дясно на ляво!
> (length . words) "Hello world"
2
> map (even . length) ["Hello", "world!"]
[False, True]
> filter (odd . (+ 1)) [1..5]
[2, 4]
> head . filter (elem 'e') . words $ "hello world"
"hello"
$
е оператор за извикване на функция. Макар да изглежда като най-безсмисленият оператор (тъй като извикването става автоматично след space f x
), той е много полезен тъй като приоритетът му е много нисък (за разлика от space). Това позволява изпускане на скоби и е много удобно когато искаме да подадем на функция нещо като краен аргумент.
mul x y = x * y
> mul 3 $ 4 -- same as mul 3 4
12
> mul $ 3 $ 4 -- same as mul (3 4)
-- whoops, 3 is not a function!
** Compile time error
> length . words $ "hi 5"
-- OK! (length . words) "hi 5"
2
> length $ words $ "hi 5"
-- OK! length (words "hi 5")
2
> (length $ words) $ "hi 5"
-- Fail! (length words) <- words is not an array!
** Compile time error
notCompose :: (b -> c) -> (a -> b) -> (a -> c)
notCompose f g = f $ g -- nope, we try to (f g)
Секциите ни позволяват обръщане на оператор към функция. Същевременно може да подадем и ляв или десен аргумент.
myAdd = (+)
> myAdd 3 4
7
add42 = (+ 42)
> add42 10
52
> map (10 ^) . filter (> 0) $ [-2 .. 2]
[10, 100]
Позволяват ни да използваме функция, все едно е оператор!
vowels :: [Char]
vowels = ['a', 'e', 'i', 'o', 'u']
skipVowels :: String -> String
skipVowels x = filter (not . (`elem` vowels)) x
> (`elem` vowels) 'c'
False
> (`elem` vowels) 'a'
True
> skipVowels "Haskell is great"
"Hskll s grt"
onlyVowels :: String -> String
onlyVowels x = filter (`elem` vowels) x
Е същото като:
onlyVowels' :: String -> String
onlyVowels' = filter (`elem` vowels)
Тъй като
filter :: [a -> Bool] -> [a]
Всички функции са curried
! Ние сме подали само един аргумент и ни е върната функция с оставащия!