sc-lectures/3.md
2020-05-18 00:04:02 +03:00

866 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
theme: superblack
author: Ilya Kostyuchenko
---
# Функциональное программирование
---
## Напоминалочка
```{ .haskell }
class Functor f where
fmap :: (a -> b) -> f a -> f b
```
```{ .haskell .fragment }
class Functor f => Alternative f where
(<|>) :: f a -> f a -> f a
empty :: f a
```
---
## Библиотека про математику
#### (на джаве)
```{ .java .fragment }
static int add(int x, int y) {
return x + y;
}
```
["Нам для сертификации надо чтобы каждая функция логировала свой вызов."]{.fragment}
---
### Вариант 1
```{ .java .fragment }
static int add(int x, int y) {
log += "add";
return x + y;
}
```
1. Неявная зависимость
2. Невозможно рефакторить
3. Почему функция для складывания чисел умеет логи?
---
### Вариант 2
```{ .java .fragment }
static Pair<Integer, String> add(int x, int y, string log) {
return new Pair(x + y, log + "add");
}
```
1. ~~Неявная зависимость~~
2. ~~Невозможно рефакторить~~
3. Неудобно программировать
3. Почему функция для складывания чисел умеет логи?
---
### Вариант ООП
```{ .java .fragment }
static int add(int x, int y, Logger log) {
Logger.log("add");
return x + y;
}
```
1. ~~Неявная зависимость~~
2. ~~Невозможно рефакторить~~
3. Неудобно программировать
3. ~~Почему функция для складывания чисел умеет логи?~~
---
```{ .haskell }
add :: Int -> Int -> (Int, String)
add x y = (x + y, "add " + show x + show y)
```
[Неудобно программировать]{ .fragment }
```{ .haskell .fragment }
add :: Int -> Int -> Int
times :: Int -> Int -> Int
add8AndDouble :: Int -> Int
add8AndDouble = times 2 . add 8
```
---
```{ .haskell }
add :: Int -> Int -> (Int, String)
times :: Int -> Int -> (Int, String)
```
```{ .haskell .fragment }
add8AndDouble :: Int -> (Int, String)
add8AndDouble x = (y2, log1 <> log2)
where
(y1, log1) = add 8 x
(y2, log2) = times 2 y1
```
![](images/ohno.png){height=200px .fragment}
```{ .haskell .fragment }
add8AndDouble :: Int -> Int
add8AndDouble = times 2 . add 8
```
---
```{ .haskell }
data WithLog a = WithLog a String
```
```{ .haskell .fragment }
add :: Int -> Int -> WithLog Int
times :: Int -> Int -> WithLog Int
```
```{ .haskell .fragment }
add8AndDouble :: Int -> WithLog Int
add8AndDouble = times 2 `magic` add 8
```
```{ .haskell .fragment }
magic ::
(Int -> WithLog Int) ->
(Int -> WithLog Int) ->
(Int -> WithLog Int)
```
```{ .haskell .fragment }
magic f g x1 = WithLog x3 (log1 <> log2)
where
(WithLog x2 log1) = g x1
(WithLog x3 log2) = f x2
```
---
```{ .haskell }
magic ::
(Int -> WithLog Int) ->
(Int -> WithLog Int) ->
(Int -> WithLog Int)
```
```{ .haskell .fragment }
(<=<) ::
(Int -> WithLog Int) ->
(Int -> WithLog Int) ->
(Int -> WithLog Int)
```
```{ .haskell .fragment }
add8AndDouble :: Int -> WithLog Int
add8AndDouble = times 2 <=< add 8
```
---
```{ .haskell }
factor :: Int -> WithLog [Int]
sum :: [Int] -> WithLog Int
```
```{ .haskell .fragment }
sumFactors :: Int -> WithLog [Int]
sumFactors = sum <=< factor -- Doesn't work :(
```
```{ .haskell .fragment }
(<=<) ::
(Int -> WithLog Int) ->
(Int -> WithLog Int) ->
(Int -> WithLog Int)
```
```{ .haskell .fragment }
(<=<) ::
(b -> WithLog c ) ->
(a -> WithLog b ) ->
(a -> WithLog c )
```
```{ .haskell .fragment }
(.) ::
(b -> c ) ->
(a -> b ) ->
(a -> c )
```
---
```{ .haskell }
getCoffee :: Order -> Maybe Coffee
payForCoffee :: Coffee -> Maybe Payment
```
```{ .haskell .fragment }
buyCoffee :: Order -> Maybe Payment
buyCoffee = payForCoffee <=< getCoffee
```
```{ .haskell .fragment }
getCoffee :: Order -> Either CoffeeError Coffee
payForCoffee :: Coffee -> Either CoffeeError Payment
```
```{ .haskell .fragment }
buyCoffee :: Order -> Either CoffeeError Payment
buyCoffee = payForCoffee <=< getCoffee
```
```{ .haskell .fragment }
(<=<) ::
Composable m =>
(b -> m c) ->
(a -> m b) ->
(a -> m c)
```
---
```{ .haskell }
class Applicative m => Monad m where
(<=<) :: (b -> m c) -> (a -> m b) -> (a -> m c)
```
```{ .haskell .fragment }
instance Monad Maybe where
(<=<) f g x = case g x of
Nothing -> Nothing
Just x2 -> f x2
```
```{ .haskell .fragment }
instance Monad (Either e) where
(<=<) f g x = case g x of
Left e -> Left e
Right x2 -> f x2
```
```{ .haskell .fragment }
instance Monad WithLog where
(<=<) f g x = WithLog x3 (log1 <> log2)
where
(WithLog x2 log1) = g x
(WithLog x3 log2) = f x2
```
---
### Больше операторов!
```{ .haskell .fragment }
(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
```
```{ .haskell .fragment }
buyCoffee :: Order -> Maybe Payment
buyCoffee = payForCoffee <=< getCoffee
```
```{ .haskell .fragment }
flip :: (a -> b -> c) -> (b -> a -> c)
flip f b a = f a b
```
```{ .haskell .fragment }
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
(>=>) = flip (<=<)
```
```{ .haskell .fragment }
buyCoffee = getCoffee >=> payForCoffee
```
---
### Маленькое отступление
`f :: () -> a` эквивалентно `f :: a`
[Потому что у `()` всего одно значение (`()`), а любая функция для одинаковых аргументов всегда возвращает одинаковые значения (потому что по-другому невозможно).]{ .fragment }
---
```{ .haskell }
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
```
```{ .haskell .fragment }
(>>=) :: Monad m => m b -> (b -> m c) -> m c
```
```{ .haskell .fragment }
buyCoffee :: Order -> Maybe Payment
buyCoffee order = getCoffee order >>= payForCoffee
```
```{ .haskell .fragment }
maybeBuyCoffee :: Maybe Order -> Maybe Payment
maybeBuyCoffee mOrder = mOrder >>= buyCoffee
```
```{ .haskell .fragment }
(>>=) :: Monad m => m b -> (b -> m c) -> m c
```
```{ .haskell .fragment }
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
```
```{ .haskell .fragment }
(() -> m b) -> (b -> m c) -> (() -> m c)
```
```{ .haskell .fragment }
m b -> (b -> m c) -> m c
```
---
```{ .haskell }
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
```
```{ .haskell }
(>>=) :: Monad m => m b -> (b -> m c) -> m c
(>>=) mb bmc = ( (\() -> mb) >=> bmc ) ()
```
```{ .haskell .fragment }
(>>=) mb bmc = ( (\() -> mb) >=> bmc ) ()
-- () -> m b |
-- b -> m c
--
-- ^-----------------------^
-- () -> m c
```
---
### Время раскрыть ложь
```{ .haskell }
class Applicative m => Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
```
```{ .haskell .fragment }
-- Law:
ma :: m a
(ma >>= return) == ma
```
```{ .haskell .fragment }
-- Law:
f :: a -> m b
(f >=> return) == f
```
[`return` -- нейтральный элемент по `>=>`]{.fragment}
```{ .haskell .fragment }
-- Law:
x :: a
f :: a -> m b
(return x >>= f) == f x
```
---
```{ .haskell }
instance Monad Maybe where
ma >>= f = case ma of
Nothing -> Nothing
Just a -> f a
return a = Just a
```
```{ .haskell .fragment }
instance Monad (Either e) where
ma >>= f = case ma of
Left e -> Left e
Right a -> f a
return a = Right a
```
```{ .haskell .fragment }
instance Monad WithLog where
(WithLog a log1) >>= f = WithLog b (log1 <> log2)
where
WithLog b log2 = f a
return a = WithLog a ""
```
---
## More Monads!
```{ .haskell }
type Markup = Int
getCoffee :: Markup -> Order -> Coffee
payForCoffee :: Markup -> Coffee -> Payment
buyCoffee :: Markup -> Order -> Payment
buyCoffee markup = payForCoffee markup . getCoffee markup
```
```{ .haskell .fragment }
data Reader r a = Reader
{ runReader :: r -> a
}
```
[`r` -- то, что у нас в "окружении".]{ .fragment }
---
```{ .haskell }
data Reader r a = Reader { runReader :: r -> a }
getCoffee :: Order -> Markup -> Coffee
payForCoffee :: Coffee -> Markup -> Payment
```
```{ .haskell .fragment }
getCoffee :: Order -> Reader Markup Coffee
payForCoffee :: Coffee -> Reader Markup Payment
```
```{ .haskell .fragment }
type PricingEnv = Reader Markup
```
```{ .haskell .fragment }
getCoffee :: Order -> PricingEnv Coffee
payForCoffee :: Coffee -> PricingEnv Payment
```
```{ .haskell .fragment }
buyCoffee :: Order -> PricingEnv Payment
buyCoffee = payForCoffee <=< getCoffee
```
```{ .haskell .fragment }
payment :: Payment
payment = runReader (buyCoffee order) markup
```
---
```{ .haskell }
data Reader r a = Reader
{ runReader :: r -> a
}
instance Monad (Reader r) where
return a = Reader (\_ -> a)
(Reader g) >>= f = Reader ( \r -> runReader (f (g r)) r )
```
```{ .haskell .fragment}
(>>=) :: Reader r a -> (a -> Reader r b) -> Reader r b
(Reader g) >>= f = Reader ( \r -> runReader (f (g r)) r )
-- ^---^
-- a
-- ^-------^
-- Reader r b
-- ^-----------------^
-- r -> b
```
---
```{ .haskell }
type Price = Int
getCoffeePrice :: Markup -> Coffee -> Price
getDiscount :: Markup -> Coffee -> Price
getPayment :: Markup -> Price -> Payment
payForCoffee :: Markup -> Coffee -> Payment
payForCoffee markup coffee = getPayment markup amount
where
coffeePrice = getCoffeePrice markup coffee
discount = getDiscount markup coffee
amount = coffeePrice - discount
```
```{ .haskell .fragment }
type PricingEnv = Reader Markup
getCoffeePrice :: Coffee -> PricingEnv Price
getDiscount :: Coffee -> PricingEnv Price
getPayment :: Price -> PricingEnv Payment
payForCoffee :: Coffee -> PricingEnv Payment
payForCoffee coffee =
coffee >>= getCoffeePrice >>= \coffeePrice ->
coffee >>= getDiscount >>= \discount ->
getPayment (coffeePrice - discount)
```
---
## Do syntax
```{ .haskell .fragment }
bar :: Reader r a
qux :: a -> Reader r b
foo :: Reader r b
foo = bar >>= \x -> qux x
```
```{ .haskell .fragment }
foo = do
-- (x :: a) <- (bar :: Reader r a)
x <- bar
qux x
```
```{ .haskell .fragment }
foo = do
-- (x :: a) <- (bar :: Reader r a)
x <- bar
-- (y :: b) <- (qux x :: Reader r b)
y <- qux x
return y
```
```{ .haskell .fragment }
foo = bar >>= \x -> qux x >>= return
```
---
```{ .haskell }
type PricingEnv = Reader Markup
getCoffeePrice :: Coffee -> PricingEnv Price
getDiscount :: Coffee -> PricingEnv Price
getPayment :: Price -> PricingEnv Payment
payForCoffee :: Coffee -> PricingEnv Payment
payForCoffee coffee =
coffee >>= getCoffeePrice >>= \coffeePrice ->
coffee >>= getDiscount >>= \discount ->
getPayment (coffeePrice - discount)
```
```{ .haskell .fragment }
payForCoffee :: Coffee -> PricingEnv Payment
payForCoffee coffee = do
coffeePrice <- getCoffeePrice coffee
discount <- getDiscount coffee
getPayment (coffeePrice - discount)
```
---
## Let statement
```{ .haskell }
payForCoffee :: Coffee -> PricingEnv Payment
payForCoffee coffee = do
coffeePrice <- getCoffeePrice coffee
discount <- getDiscount coffee
getPayment (coffeePrice - discount)
```
```{ .haskell .fragment }
payForCoffee :: Coffee -> PricingEnv Payment
payForCoffee coffee = do
coffeePrice <- getCoffeePrice coffee
discount <- getDiscount coffee
let amount = coffeePrice - discount
getPayment amount
```
---
```{ .haskell }
type PricingEnv = Reader Markup
type Price = Int
data Coffee = Coffee { getCoffeePrice :: Price }
getCoffeePrice :: Coffee -> PricingEnv Price
```
```{ .haskell .fragment }
data Reader r a = Reader { runReader :: r -> a}
```
```{ .haskell .fragment }
ask :: Reader r r
ask = Reader (\r -> r)
```
```{ .haskell .fragment }
getCoffeePrice :: Coffee -> PricingEnv Price
getCoffeePrice coffee = do
-- (markup :: Markup) <- (ask :: PricingEnv Markup)
markup <- ask
let coffePrice = getCoffeePrice coffee
return (coffee + markup)
```
---
```{ .haskell }
data Order = Order { orderCoffee :: Coffee, orderId :: Int }
```
```{ .haskell .fragment }
createOrder :: Coffee -> Order
```
[Откуда брать `orderId`?]{ .fragment }
```{ .haskell .fragment }
createOrder :: Coffee -> Reader Int Order
createOrder coffee = do
newId <- ask
return (Order coffee newId)
```
[🎉]{ .fragment }
```{ .haskell .fragment }
createOrders :: [Coffee] -> Reader Int [Order]
```
```{ .haskell .fragment }
mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]
```
```{ .haskell .fragment }
createOrders = mapM createOrder
```
---
```{ .haskell }
createOrders :: [Coffee] -> Reader Int [Order]
createOrders = mapM createOrder
```
[Все `id` будут одинаковые.]{ .fragment }
[`Reader` эквивалентно глобальной **константе**.]{ .fragment }
[Айдишники -- скорее глобальная **переменная**.]{ .fragment }
[-- Но у нас же нет переменных!]{ .fragment }
[-- Да, нет!]{ .fragment }
---
## State!
```{ .haskell .fragment }
data State s a = ... -- s -- само состояние
get :: State s s
put :: s -> State s ()
```
```{ .haskell .fragment }
data State s a = State { runState :: s -> (a, s) }
get :: State s s
get = State (\s -> (s, s))
put :: s -> State s ()
put s = State (\_ -> ((), s))
```
---
```{ .haskell }
data State s a = State { runState :: s -> (a, s) }
```
```{ .haskell .fragment }
class Monad (State s) where
return x = State (\s -> (x, s))
(>>=) :: State s a -> (a -> State s b) -> State s b
(State f) >>= g = State go
where
go s = runState (g a) s'
where
(a, s') = f s
```
---
### Айдишники!
```{ .haskell .fragment }
getNewId :: State Int Int
getNewId = do
newId <- get
put (newId + 1)
return newId
```
[-- Это какой-то процедурный языке получается!]{ .fragment }
[-- Лучше!*]{ .fragment }
```{ .haskell .fragment }
createOrder :: Coffee -> State Int Order
createOrder coffee = do
newId <- getNewId
return (Order coffee newId)
createOrders :: [Coffee] -> State Int [Order]
createOrders = mapM createOrder
```
---
## Еще монады!
```{ .haskell .fragment }
class Monad [] where
return x = [x]
(>>=) :: [a] -> (a -> [b]) -> [b]
[] >>= _ = []
(a : aa) >>= f = f a <> (a >>= f)
```
[Контекст недетерменированности (нескольких вариантов)]{ .fragment }
```{ .haskell .fragment }
grid = do
x <- [1..5]
y <- [1..5]
return (x, y)
-- [(1,1),(1,2),(1,3),(1,4),(1,5),(2,1),(2,2),(2,3),(2,4),(2,5),(3,1),(3,2),(3,3),(3,4),(3,5),(4,1),(4,2),(4,3),(4,4),(4,5),(5,1),(5,2),(5,3),(5,4),(5,5)]
```
---
Тривиальная монада
```{ .haskell .fragment }
data Identity a = Identity { runIdentity :: a }
class Monad Identity where
return x = Identity x
(>>=) :: Identity a -> (a -> Identity b) -> Identity b
(Identity a) >>= f = Identity (f a)
```
---
```{ .haskell }
class Applicative m => Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
```
[Монада -- перегрузка композиции функций.]{.fragment }
```{ .haskell .fragment }
class Functor m => Applicative m where
pure :: a -> m a
(<*>) :: m (a -> b) -> m a -> m b
```
[Аппликативный функтор -- перегрузка применения функций.]{.fragment }
[-- Почему не `(a -> b) -> f a -> f b`.]{.fragment }
[-- Это `fmap`. (И функции нескольких аргументов)]{.fragment }
---
```{ .haskell }
(a -> b) -> f a -> f b
```
```{ .haskell .fragment }
(a -> (b -> c)) -> f a -> f (b -> c)
```
```{ .haskell .fragment }
getMarkup :: Env Markup
getPrice :: Env Price
getPayment :: Markup -> Price -> Payment
```
```{ .haskell .fragment }
payment :: Env Payment
payment = fmap getPayment getMarkup <*> getPrice
```
```{ .haskell .fragment }
payment = fmap getPayment getMarkup <*> getPrice
-- ^-------------^
-- Env Markup -> Env (Price -> Payment)
```
```{ .haskell .fragment }
(<$>) = fmap
```
```{ .haskell .fragment }
payment = getPayment <$> getMarkup <*> getPrice
```
---
```{ .haskell }
(<*>) :: m (a -> b) -> m a -> m b
mf <*> ma = do
f <- mf
a <- ma
return (f a)
```
```{ .haskell .fragment }
ap :: Monad m => m (a -> b) -> m a -> m b
ap mf ma = do
f <- mf
a <- ma
return (f a)
```
```{ .haskell .fragment }
instance Monad ... where ...
instance Applicative ... where
pure = return
(<*>) = ap
```