Skip to content

Latest commit

 

History

History
318 lines (217 loc) · 8.77 KB

composition-currying.md

File metadata and controls

318 lines (217 loc) · 8.77 KB

Currying & Composition


Георги Наков, 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 приложенията:

  • правят едно нещо, но го правят добре
  • по-сложните програми са композиция на по-прости
  • ново-добавените средства се интегрират лесно със съществуващите посредством общ интерфейс - файлове
  • това позволява лесна работа, гъвкавост и продуктивност

Механизмът за слепване

  • В Unix взаимоработата на две приложения става чрез писане и четене на файлове.
  • bash, sh и дригите shells предоставя удобен начин за пренасочване на stdin и stdout посредством |.
  • | "свързва" стандарния изход на предходната програма със стандартния вход на последващата, ефективно създавайки еднопосочен канал за предаване на информация.

Механизми за слепване - Haskell

  • Haskell предоставя два основни механизма се слепване - currying и function composition.
  • тези механизми спомагат за синатктично лесно и визуално приятно преизползване на готови функци
  • това е основната причина защо Haskell програмите обикновено са по-генерични от анаглогична програма в друг език

Currying

  • В Haskell всяка функция има само 1 параметър
  • функциите на много параметри са syntactic sugar за вложени функции с по един парамеър

add :: Int -> Int -> Int
add x y = x + y

addLam :: Int -> Int -> Int
addLam = \x -> \y -> x + y

Currying - пример

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 - сигнатури

Автоматичният 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

Composition

Композицията е | във функционалните езици. Целта му е слепване на изхода на една фунцкия с входа на друга.

Ако имаме функциите:

g :: Int -> Double
g = intToDouble
f :: Double -> String
f = show

То композицията им е функцията:

c :: Int -> String
c x = f (g x)

Composition ..

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. Изразът се чете от дясно на ляво!


Composition - Примери

> (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)

Sections

Секциите ни позволяват обръщане на оператор към функция. Същевременно може да подадем и ляв или десен аргумент.

myAdd = (+)

> myAdd 3 4
7

add42 = (+ 42)

> add42 10
52

> map (10 ^) . filter (> 0) $ [-2 .. 2]
[10, 100]

Backticks

Позволяват ни да използваме функция, все едно е оператор!

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! Ние сме подали само един аргумент и ни е върната функция с оставащия!