--- theme: superblack author: Ilya Kostyuchenko --- # Функциональное программирование :::{.notes} Функциональное программирование это не про использование map filter reduce. Функциональное программирование это вообще ортогонально к ооп. ИМХО проблема с ООП -- наследование (которое тоже ортогонально к ООП) -- нет разграничения между абстракцией и реализацией. и вообще обзывание это функциональным программированием -- не совсем правильно. то, что мы сейчас будем пытаться сделать -- сделать код более переиспользуемым. сделать наши данные максимально тупыми => переиспользуемыми. пытаться комбинировать слишком умные куски -- не получится -- слишком сложно. мы хотим сделать простые куски, которые просто комбинировать и еще чтобы компилятор проверял эти наши комбинации на валидность. ::: ---

Программировать можно без боли

--- Функциональное программирование -- метапрограммирование над эффектами. --- ## Эффекты? ```{ .java .fragment } class Cafe { Coffee buyCoffee(CreditCard cc, Payments p) { Coffee cup = new Coffee(); p.charge(cc, cup.price); return cup; } } ``` --- ![](images/coffee-with-effects.png){width=450} --- ```{ .java } class Cafe { Pair buyCoffee(CreditCard cc) { Coffee cup = new Coffee(); return new Pair(cup, cup.price); } } ``` ---
![](images/coffee-without-effects.png){width=450} ![](images/coffee-without-effects-list.png){width=450 .fragment}
--- Функциональное программирование: > функции -- это тоже данные. `Int -> Bool` (функция) -- ровно такие же данные как и `Int`. Их также можно складывать в структуры, передавать как аргументы и т. п. --- # нотация ---
```java int x; ``` ```haskell x :: Int ```
```java int f(int x) ``` ```haskell f :: Int -> Int ```
```java static T f(T x) ``` ```haskell f :: a -> a ```
---
```java static T f(T x) ``` ```haskell f :: a -> a ```
```java f(x); ``` ```haskell f x ```
```java f(x, y); ``` ```{.fragment .haskell} f x y ``` ```{.fragment .haskell} f x y = (f x) y ```
:::{.notes} Left-associative Currying ::: ---
```java static T f(T x) static T g(T x) ``` ```haskell f :: a -> a g :: a -> a ```
```java f(g(x)); g(f(x)); ``` ```haskell f (g x) g (f x) ```
--- # Композиция --- ```haskell f :: a -> a g :: a -> a ``` ``` {.fragment .haskell} f (g x) ``` ``` {.fragment .haskell} (f . g) x == f (g x) ``` ``` {.fragment .haskell} h = f . g h x == f (g x) ``` --- ```haskell f :: a -> a g :: a -> a ``` ```haskell h :: a -> a h = f . g ``` ```{ .haskell .fragment } f x = x * 2 g x = x - 3 (f . g) 5 -- 4 ``` :::{.notes} моноид -- у тебя есть две штуки одного типа и ты можешь получить еще одну штуку такого же типа. Это очень простой способ рождения сложности из простоты причем тип h выводится из определения. Нам его указывать не надо. если у нас изначальные маленькие кусочки хорошо описаны, то порожденные большие кусочки будут автоматически хорошо описанными. ::: --- # Haskell --- ### Чисто функциональный Все что функция может сделать -- посмотреть на свои аргументы это вернуть значение. :::{.notes} Нельзя просто произвольно распечатать строку в консоль, нельзя просто произвольно отправить запрос на бд, нельзя просто произвольно изменить глобальную переменную. ::: --- ### Referential transparency ##### (Любую константу можно заменить на само значение) ```{.java .fragment} int x = 8; int y = x++; System.out.println(y + " might be the same as " + y); ``` ```{.java .fragment} int x = 8; System.out.println(x++ + " might be the same as " + x++); ``` :::notes Сильно упрощает рефакторинг и понимание кода. ::: --- # Immuatble ## Совсем immutable Можно не думать о порядке выражений. --- ### Нет переменных, циклов и условных переходов [Зато есть константы и нормальная рекурсия (А переходов вообще нет)]{.fragment} --- ### Очень ленивый ```{.haskell .fragment} xs = [4, 1, 3, 2] xs' = sort xs print xs ``` :::fragment `xs'` не нужен чтобы распечатать `xs`, поэтому сортироваться ничто не будет. (Зачем делать то, что можно не делать) ::: --- Если хотите быстро и просто потыкаться, тут есть интерктивная штука: [haskell.org](https://www.haskell.org) --- # Синтаксис --- # Функции --- ```{ .haskell } add2 :: Int -> Int add2 x = x + 2 ```` Все функции и константы всегда обозначаются словами с маленькой буквы без пробелов. (Константы это просто функции с нулем аргументов.) :::notes Мы буквально говорим что функция на таком-то аргументе равна чему-то. ::: --- ## Pattern matching ```haskell fixBuz :: Int -> String fixBuz 3 = "Fiz" fixBuz 5 = "Buz" fixBuz 15 = "FizBuz" fixBuz _ = "Some other number" ``` Так матчить можно произвольные структуры произвольного уровня вложенности. `_` -- специальное название константы, которое говорит что вам все равно что в ней лежит. :::notes функции объявляются в несколькро строк. Первая -- обьявленик типа функции, а последующие -- реализация. В общем случае тип можно не указывать, но указывать тип у высказыванй на самом верхнем уровне (не вложенные) считается хорошим тоном и улучшает выведение типов и ошибки компиляции. У функции может быть несколько реализаций: какая из них вызовется зависит от значений передаваемых аргументов. матчинг произхводится сверху вниз. если в аргемантах написано слово с маленькой буквы, то значение аргумента биндится на эту константу. ::: --- ## Структуры --- ## Произведение типов ##### (обычные поля структур) :::notes После констрктора можно укзать существующие типы, которые в нем храняться. ::: ```haskell data PersonType = Person String Int ``` ```{.haskell .fragment} vasya :: PersonType vasya = Person "Vasya" 8 -- тип можно не укзывать petya = Person "Petya" 5 ``` ```{.haskell .fragment} getName :: PersonType -> String getName (Person name _) = name ``` ```{.haskell .fragment} greetPerson :: PersonType -> String greetPerson p = "Hello, " ++ getName p ``` ```{.haskell .fragment} greetPerson petya -- "Hello, Petya" ``` --- ```{ .haskell } vasya = Person "Vasya" 8 petya = Person "Petya" 5 ``` ```{ .haskell .fragment } matchVasya :: PersonType -> Bool matchVasya (Person "Vasya" _) = True matchVasya _ = False ``` ```{ .haskell .fragment } matchVasya vasya -- True matchVasya petya -- False ``` --- ```haskell data Foo = Bar qux :: Foo qux = Bar ``` `Foo` -- тип структуры. `Bar` -- конструктор структуры. Тут у `Foo` всего одно значение `Bar`. --- ## Еще немного функций ```{ .haskell .fragment } greetPerson :: PersonType -> String greetPerson p = "Hello, " ++ getName p ``` ```{.fragment .haskell } greetPerson :: PersonType -> String greetPerson p = "Hello, " ++ name where name = getName p ``` ```{.fragment .haskell } greetPerson :: PersonType -> String greetPerson p = "Hello, " ++ name where getName' (Person name _) = name name = getName' p ``` --- ```{ .haskell } data PersonType = Person String Int getName :: PersonType -> String getName (Person name _) = name ``` ```{.fragment .haskell } greetName :: String -> String greetName name = "Hello, " ++ name ``` ```{.fragment .haskell } greetPerson :: PersonType -> String greetPerson p = greetName (getName p) ``` ```{.fragment .haskell } greetPerson :: PersonType -> String greetPerson = greetName . getName ``` ```{.fragment .haskell } greetPerson petya -- "Hello, Petya" ``` ```{.fragment .haskell } (greetName . getName) petya -- "Hello, Petya" ``` --- ## Суммы типов :::notes Функции нескольких аргументов -- странный синтаксис -- потом расскажу. А пока используем тюпли! В большинстве других языков этого нет и используются всякие странные кодирования типо нулевые указатели, и никто не просит тебя их проверять, что очень плохо. ::: ```{ .fragment .haskell } data Bool = False | True ``` ```{ .fragment .haskell } x :: Bool x = True y = False ``` ```{ .fragment .haskell } ifThenElse :: (Bool, a, a) -> a ``` ```{ .fragment .haskell } ifThenElse (True, a, _) = a ifThenElse (False, _, b) = b ``` ```{ .fragment .haskell } ifThenElse (True, "Hello", "World") -- "Hello" ``` ```{ .fragment .haskell } ifThenElse (False, "Hello", "World") -- "World" ``` --- ```{ .haskell } data CircleType = Circle Double Double Double data RectangleType = Rectangle Double Double Double Double ``` ```{ .fragment .haskell } data Shape = CircleShape CircleType | RectangleShape RectangleType ``` ```{ .fragment .haskell } surface :: Shape -> Double ``` ```{ .fragment .haskell } surface (CircleShape (Circle _ _ r)) = pi * r ^ 2 ``` ```{ .fragment .haskell } surface (RectangleShape (Rectangle x1 y1 x2 y2)) = (abs (x2 - x1)) * (abs (y2 - y1)) ``` ```{ .fragment .haskell } shape = CircleShape (Circle 0 0 2) surface shape -- 12.566370614359172 ``` ```{ .fragment .haskell } otherShape = RectangleShape (Rectangle 1 2 3 4) surface otherShape -- 4.0 ``` --- # И еще немного функций --- ## Лямбда-выражения ```{ .haskell .fragment } add8 :: Int -> Int ``` ```{ .haskell .fragment } add8 x = x + 8 ``` ```{ .haskell .fragment } add8 = \x -> x + 8 ``` [λ -- `\` (λ печатать тяжело)]{.fragment} ```{ .haskell .fragment } foo :: (Int -> Int) -> Int ``` ```{ .haskell .fragment } foo add8 ``` ```{ .haskell .fragment } foo (\x -> x + 8) ``` --- ### Давайте придумаем синтаксис для функции нескольких аргументов! --- ```{ .haskell } x, y :: Int x = 42 y = 69 ``` ```{ .haskell .fragment } xPlusY :: Int xPlusY = add x y ``` [Применение функции -- лево-ассоциативно]{.fragment} ```{ .haskell .fragment } xPlusY = (add x) y ``` ```{ .haskell .fragment } xPlusY = f y f = add x ``` ```{ .haskell .fragment } f :: Int -> Int ``` ```{ .haskell .fragment } add :: Int -> (Int -> Int) ``` --- ```{ .haskell } add :: Int -> (Int -> Int) ``` [Тип `->` -- право-ассоциативный]{.fragment} ```{ .haskell .fragment } add :: Int -> Int -> Int ``` ```{ .haskell .fragment } add a b = a + b ``` ```{ .haskell .fragment } add = \a b -> a + b ``` --- Любая функция берет строго один аргумент. Функция нескольких аргументов все равно берет строго одтн аргумент и возвращает функцию, которая берет следующий. *(И из-за того, что применение функции лево-ассоциативно, вызов таких не трубует особого синтаксиса.)* --- ### Currying ```{ .haskell .fragment } add :: Int -> Int -> Int add a b = a + b ``` ```{ .haskell .fragment } add8 :: Int -> Int ``` ```{ .haskell .fragment } add :: Int -> (Int -> Int) ``` ```{ .haskell .fragment } add8 = add 8 ``` ```{ .haskell .fragment } add8 3 -- 11 ``` --- ### Funny fact [Оператор (например `+`) -- функция, название которой не содержит буквы и цифры.]{.fragment} ```{ .haskell .fragment } x +&+ y = x + y ``` ```{ .haskell .fragment } 8 +&+ 9 -- 17 ``` --- ### Funny fact 2 [Оператор можно превратить в функцию, окружив его скобками.]{.fragment} ```{ .haskell .fragment } 8 +&+ 9 -- 17 ``` ```{ .haskell .fragment } (+&+) 8 9 -- 17 ``` ```{ .haskell .fragment } add :: Int -> Int -> Int add x y = x + y ``` ```{ .haskell .fragment } add = (+&+) ``` ```{ .haskell .fragment } add = (+) ``` --- ### Funny fact 3 [Функцию можно превратить в оператор, окружив ее обратными кавычками.]{.fragment} ```{ .haskell .fragment } add :: Int -> Int -> Int add x y = x + y ``` ```{ .haskell .fragment } add 8 9 -- 17 ``` ```{ .haskell .fragment } 8 `add` 9 -- 17 ``` --- # Список --- ### Односвязный список ```{ .haskell .fragment } data IntList = Nil | Cons Int IntList ``` ```{ .haskell .fragment } Cons :: Int -> IntList -> IntList Nil :: IntList ``` ```{ .haskell .fragment } nums :: IntList nums = 1 `Cons` (2 `Cons` (3 `Cons` Nil)) ``` ```{ .haskell .fragment } sum :: IntList -> Int ``` ```{ .haskell .fragment } sum (Cons x xs) = x + sum xs ``` ```{ .haskell .fragment } sum Nil = 0 sum (Cons x xs) = x + sum xs ``` ```{ .haskell .fragment } sum nums -- 6 ``` --- ```{ .haskell } take :: Int -> IntList -> IntList ``` ```{ .haskell .fragment } take _ Nil = Nil take 0 _ = Nil take n (Cons x xs) = Cons x (take (n - 1) xs) ``` ```{ .haskell .fragment } nums :: IntList nums = 1 `Cons` (2 `Cons` (3 `Cons` Nil)) ``` ```{ .haskell .fragment } take 2 nums -- Cons 1 (Cons 2 Nil) take 1029 nums -- Cons 1 (Cons 2 (Cons 3 Nil)) take 0 nums -- Nil ``` --- ```{ .haskell } repeat :: Int -> IntList ``` ```{ .haskell .fragment } repeat n = Cons n (repeat n) ``` ```{ .haskell .fragment } repeat 8 -- Cons 8 (Cons 8 (Cons 8 (Cons 8 (Cons 8 (Cons 8 (Cons 8 (Cons 8 (Cons 8 (Cons 8 (... ``` ```{ .haskell .fragment } (take 3 . repeat) 8 -- Cons 8 (Cons 8 (Cons 8 Nil)) ``` ```{ .haskell .fragment } (sum . take 3 . repeat) 8 -- 24 ``` ---
Наша самодеятельность В стандартной библиотеке
```haskell IntList ``` ```haskell [Int] ```
```haskell Nil ``` ```haskell [] ```
```haskell Cons ``` ```haskell : ```
```haskell Cons 3 (Cons 4 Nil) ``` ``` {.fragment .haskell } 3 : 4 : [] ``` ``` {.fragment .haskell } [3, 4] ```
--- ```{ .haskell } data IntList = Nil | Cons Int IntList ``` ```{ .haskell .fragment } data [Int] = [] | Int : [Int] ``` --- `repeat`, `sum` и `take` тоже есть в стандартной библиотеке. --- ```{ .haskell } repeat 8 -- [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, ... ``` ```{ .haskell .fragment } [8 ..] -- [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ... ``` ```{ .haskell .fragment } [8 .. 11] -- [8, 9, 10, 11] ``` ```{ .haskell .fragment } [8 .. 8] -- [8] ``` ```{ .haskell .fragment } [8 .. 5] -- [] ``` --- # QuickSort --- ```{ .haskell } quicksort :: [Int] -> [Int] ``` ```{ .haskell .fragment} quicksort (x:xs) = quicksort smaller ++ [x] ++ quicksort larger where smaller = filter (< x) xs larger = filter (>= x) xs ``` --- ```{ .haskell } filter :: (Bool -> Int) -> [Int] -> [Int] ``` ```{ .haskell .fragment} filter f (x:xs) = if f x then x:(filter f xs) else filter f xs ``` ```{ .haskell .fragment} filter _ [] = [] filter f (x:xs) = if f x then filter f xs else x:(filter f xs) ``` --- ```{ .haskell } quicksort :: [Int] -> [Int] quicksort (x:xs) = quicksort smaller ++ [x] ++ quicksort larger where smaller = filter (< x) xs larger = filter (>= x) xs filter _ [] = [] filter f (x:xs) = if f x then filter f xs else x:(filter f xs) ``` --- ```{ .haskell } quicksort :: [Int] -> [Int] quicksort [] = [] quicksort (x:xs) = quicksort smaller ++ [x] ++ quicksort larger where smaller = filter (< x) xs larger = filter (>= x) xs filter _ [] = [] filter f (x:xs) = if f x then filter f xs else x:(filter f xs) ``` ```{ .haskell .fragment} quicksort [2, 1, 3, 4] -- [1, 2, 3, 4] ``` --- `filter` тоже есть в стандартной библиотеке. --- # Еще немного структур --- ## Параметрический полиморфизм ### (Дженерики) ```{ .haskell .fragment } data IntList = Cons Int IntList | Nil ``` :::notes Мы научиоись делать список интов. А что если мы хотим сделать просто список. Чего-нибудь. ::: ```{ .haskell .fragment } data List a = Cons a (List a) | Nil ``` ```{ .java .fragment } class List { ... ``` ```{ .haskell .fragment } ints :: List Int ints = Cons 1 (Cons 2 (Cons 3 Nil)) ``` ```{ .haskell .fragment } -- Типы как всегда можно не писать strings :: List String strings = Cons "one" (Cons "two" (Cons "three" Nil)) ``` --- ```{ .haskell } things = Cons "one" (Cons 2 Nil) ``` ```fragment • Couldn't match type ‘Int’ with ‘String’ Expected type: List String Actual type: List Int | 10 | things = Cons "one" (Cons 2 Nil) | ^^^^^^^^^^^^ ``` ```{ .haskell .fragment } data List a = Cons a (List a) | Nil ``` [`a` должен всегда быть `a`.]{.fragment} --- ```{ .haskell } take :: Int -> IntList -> IntList take _ Nil = Nil take 0 _ = Nil take n (Cons x xs) = Cons x (take (n - 1) xs) ``` ```{ .haskell .fragment } take :: Int -> List a -> List a take _ Nil = Nil take 0 _ = Nil take n (Cons x xs) = Cons x (take (n - 1) xs) ``` :::notes Поменялся только тип! ::: --- # Где и как смотреть "стандартную библиотеку" --- 1. [Hackage](https://hackage.haskell.org/package/base) (Там вам нужен только пакет `base`. Ссылка ведет прямо на него.) Еще если там нажать `s`, то будет поиск. 2. [Hoogle](https://hoogle.haskell.org) Это поиск по типам. Например: `Int -> [Int] -> [Int]` (Тут вам опять же нужен только пакет `base`. Нужно чтобы справа было \"package:base\".)