2020-02-02 17:15:59 +03:00
---
theme: superblack
author: Ilya Kostyuchenko
---
# Функциональное программирование
:::{.notes}
Функциональное программирование это не про использование map filter
reduce. Функциональное программирование это вообще ортогонально к ооп.
ИМХО проблема с ООП -- наследование (которое тоже ортогонально к ООП)
-- нет разграничения между абстракцией и реализацией. и вообще
обзывание это функциональным программированием -- не совсем правильно.
то, что мы сейчас будем пытаться сделать -- сделать код более
переиспользуемым. сделать наши данные максимально тупыми =>
переиспользуемыми. пытаться комбинировать слишком умные куски -- не
получится -- слишком сложно. мы хотим сделать простые куски, которые
просто комбинировать и еще чтобы компилятор проверял эти наши комбинации
на валидность.
:::
2020-02-02 21:00:38 +03:00
---
< section data-background = "images/synthwave.jpeg" >
< h1 > Программировать можно без боли< / h1 >
< / section >
2020-02-02 17:15:59 +03:00
---
2020-02-03 00:28:04 +03:00
Функциональное программирование -- метапрограммирование над эффектами.
2020-03-03 00:26:55 +03:00
---
## Эффекты?
```{ .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< Coffee , Charge > buyCoffee(CreditCard cc) {
Coffee cup = new Coffee();
return new Pair(cup, cup.price);
}
}
```
---
< table >
< tr >
< td >
![](images/coffee-without-effects.png){width=450}
< / td >
< td >
![](images/coffee-without-effects-list.png){width=450 .fragment}
< / td >
< / tr >
< / table >
2020-02-03 00:28:04 +03:00
---
2020-02-02 17:15:59 +03:00
Функциональное программирование:
> функции -- это тоже данные.
`Int -> Bool` (функция) -- ровно такие же данные как и `Int` . Их также
можно складывать в структуры, передавать как аргументы и т. п.
---
# нотация
---
< table width = "100%" >
< tr >
< td >
```java
int x;
```
< / td >
< td >
```haskell
x :: Int
```
< / td >
< / tr >
< tr class = "fragment" >
< td >
```java
int f(int x)
```
< / td >
< td >
```haskell
f :: Int -> Int
```
< / td >
< / tr >
< tr class = "fragment" >
< td >
```java
static < T > T f(T x)
```
< / td >
< td >
```haskell
f :: a -> a
```
< / td >
< / tr >
< / table >
---
< table width = "100%" >
< tr >
< td >
```java
static < T > T f(T x)
```
< / td >
< td >
```haskell
f :: a -> a
```
< / td >
< / tr >
< tr >
< td class = "fragment" >
```java
f(x);
```
< / td >
< td class = "fragment" >
```haskell
f x
```
< / td >
< / tr >
< tr class = "fragment" >
< td >
```java
f(x, y);
```
< / td >
< td >
```{.fragment .haskell}
f x y
```
```{.fragment .haskell}
f x y = (f x) y
```
< / td >
< / td >
< / tr >
< / table >
:::{.notes}
Left-associative
Currying
:::
---
< table width = "100%" >
< tr >
< td >
```java
static < T > T f(T x)
static < T > T g(T x)
```
< / td >
< td >
```haskell
f :: a -> a
g :: a -> a
```
< / td >
< / tr >
< tr >
< td class = "fragment" >
```java
f(g(x));
g(f(x));
```
< / td >
< td class = "fragment" >
```haskell
f (g x)
g (f x)
```
< / td >
< / tr >
< / table >
---
# Композиция
---
```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
```
2020-03-03 00:26:55 +03:00
```{ .haskell .fragment }
f x = x * 2
g x = x - 3
(f . g) 5
-- 4
```
<!-- ### моноид {#моноид .fragment} -->
2020-02-02 17:15:59 +03:00
:::{.notes}
моноид -- у тебя есть две штуки одного типа и ты можешь получить еще
одну штуку такого же типа. Это очень простой способ рождения сложности
из простоты причем тип h выводится из определения. Нам е г о указывать не
надо. если у нас изначальные маленькие кусочки хорошо описаны, то
порожденные большие кусочки будут автоматически хорошо описанными.
:::
2020-03-03 00:26:55 +03:00
<!-- - - -
2020-02-02 17:15:59 +03:00
## Моноид
[Бинарная асоциативная операция]{.fragment}
```{.fragment .haskell}
(.) :: (a -> a) -> (a -> a) -> (a -> a)
```
[Нейтральный элемент]{.fragment}
```{.fragment .haskell}
id :: a -> a
id x = x
2020-03-03 00:26:55 +03:00
``` -->
2020-02-02 17:15:59 +03:00
---
# Haskell
---
2020-02-02 21:04:49 +03:00
### Чисто функциональный
2020-02-02 17:15:59 +03:00
В с е что функция может сделать -- посмотреть на свои аргументы это
вернуть значение.
:::{.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
Сильно упрощает рефакторинг и понимание кода.
:::
---
2020-03-03 00:26:55 +03:00
# Immuatble
## Совсем immutable
Можно не думать о порядке выражений.
---
2020-02-02 17:15:59 +03:00
### Нет переменных, циклов и условных переходов
[Зато есть константы и нормальная рекурсия (А переходов вообще нет)]{.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
````
В с е функции и константы всегда обозначаются словами с маленькой буквы
без пробелов.
(Константы это просто функции с нулем аргументов.)
2020-03-03 00:26:55 +03:00
:::notes
Мы буквально говорим что функция на таком-то аргументе равна чему-то.
:::
2020-02-02 17:15:59 +03:00
---
2020-03-03 00:26:55 +03:00
## Pattern matching
2020-02-02 17:15:59 +03:00
```haskell
fixBuz :: Int -> String
2020-03-03 00:26:55 +03:00
fixBuz 3 = "Fiz"
fixBuz 5 = "Buz"
fixBuz 15 = "FizBuz"
fixBuz _ = "Some other number"
2020-02-02 17:15:59 +03:00
```
Так матчить можно произвольные структуры произвольного уровня
вложенности.
`_` -- специальное название константы, которое говорит
что вам все равно что в ней лежит.
:::notes
функции объявляются в несколькро строк. Первая -- обьявленик типа
2020-02-02 21:07:21 +03:00
функции, а последующие -- реализация. В общем случае тип можно не
2020-02-02 17:15:59 +03:00
указывать, но указывать тип у высказыванй на самом верхнем уровне (не
вложенные) считается хорошим тоном и улучшает выведение типов и ошибки
компиляции. У функции может быть несколько реализаций: какая из них
вызовется зависит от значений передаваемых аргументов. матчинг
произхводится сверху вниз. если в аргемантах написано слово с маленькой
буквы, то значение аргумента биндится на эту константу.
:::
---
## Структуры
---
## Произведение типов
##### (обычные поля структур)
:::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"
```
---
2020-03-03 00:26:55 +03:00
```{ .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` .
---
2020-02-02 17:15:59 +03:00
## Еще немного функций
```{ .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
Функции нескольких аргументов -- странный синтаксис -- потом расскажу. А пока используем тюпли!
2020-03-03 00:26:55 +03:00
В большинстве других языков этого нет и используются всякие странные кодирования типо нулевые указатели, и никто не просит тебя их проверять, что очень плохо.
2020-02-02 17:15:59 +03:00
:::
```{ .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"
```
---
2020-02-02 21:03:30 +03:00
```{ .haskell }
2020-02-02 17:15:59 +03:00
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
```
---
Любая функция берет строго один аргумент.
Функция нескольких аргументов все равно берет строго одтн аргумент и
2020-02-02 21:07:21 +03:00
возвращает функцию, которая берет следующий.
2020-02-02 17:15:59 +03:00
*(И из-за того, что применение функции лево-ассоциативно, вызов таких не
трубует о с о б о г о синтаксиса.)*
---
### 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}
2020-03-03 00:26:55 +03:00
```{ .haskell .fragment }
8 +& + 9
-- 17
```
```{ .haskell .fragment }
(+& +) 8 9
-- 17
```
2020-02-02 17:15:59 +03:00
```{ .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 }
2020-03-03 00:26:55 +03:00
data IntList = Nil | Cons Int IntList
2020-02-02 17:15:59 +03:00
```
```{ .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
```
---
< table width = "100%" >
< tr >
< td >
Наша самодеятельность
< / td >
< td >
В стандартной библиотеке
< / td >
< / tr >
< tr class = "fragment" >
< td >
```haskell
IntList
```
< / td >
< td >
```haskell
[Int]
```
< / td >
< tr class = "fragment" >
< td >
```haskell
Nil
```
< / td >
< td >
```haskell
[]
```
< / td >
< / tr >
< tr class = "fragment" >
< td >
```haskell
Cons
```
< / td >
< td >
```haskell
:
```
< / td >
< / tr >
< tr class = "fragment" >
< td >
```haskell
Cons 3 (Cons 4 Nil)
```
< / td >
< td >
``` {.fragment .haskell }
3 : 4 : []
```
``` {.fragment .haskell }
[3, 4]
```
< / td >
< / tr >
< / table >
---
2020-03-03 00:26:55 +03:00
```{ .haskell }
data IntList = Nil | Cons Int IntList
```
```{ .haskell .fragment }
data [Int] = [] | Int : [Int]
```
---
2020-02-02 17:15:59 +03:00
`repeat` , `sum` и `take` тоже есть в стандартной библиотеке.
---
2020-03-03 00:26:55 +03:00
```{ .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]
-- []
```
---
2020-02-02 17:15:59 +03:00
# 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
2020-03-03 00:26:55 +03:00
then x:(filter f xs)
else filter f xs
2020-02-02 17:15:59 +03:00
```
```{ .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` тоже есть в стандартной библиотеке.
---
2020-02-02 17:48:36 +03:00
# Еще немного структур
---
2020-03-03 00:26:55 +03:00
## Параметрический полиморфизм
2020-02-02 17:48:36 +03:00
### (Дженерики)
```{ .haskell .fragment }
data IntList = Cons Int IntList | Nil
```
:::notes
Мы научиоись делать список интов. А что если мы хотим сделать просто список. Чего-нибудь.
:::
```{ .haskell .fragment }
data List a = Cons a (List a) | Nil
```
2020-03-03 00:26:55 +03:00
```{ .java .fragment }
class List< T > { ...
```
2020-02-02 17:48:36 +03:00
```{ .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 }
2020-03-03 00:26:55 +03:00
things = Cons "one" (Cons 2 Nil)
2020-02-02 17:48:36 +03:00
```
```fragment
• Couldn't match type ‘ Int’ with ‘ String’
Expected type: List String
Actual type: List Int
|
2020-03-03 00:26:55 +03:00
10 | things = Cons "one" (Cons 2 Nil)
| ^^^^^^^^^^^^
2020-02-02 17:48:36 +03:00
```
```{ .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
Поменялся только тип!
:::
---
2020-02-02 17:15:59 +03:00
# Где и как смотреть "стандартную библиотеку"
---
1. [Hackage ](https://hackage.haskell.org/package/base )
(Там вам нужен только пакет `base` . Ссылка ведет прямо на него.)
Еще если там нажать `s` , то будет поиск.
2. [Hoogle ](https://hoogle.haskell.org )
Это поиск по типам. Например: `Int -> [Int] -> [Int]` (Тут
вам опять же нужен только пакет `base` . Нужно чтобы справа было
2020-02-02 17:17:33 +03:00
\"package:base\".)