diff --git a/4.html b/4.html new file mode 100644 index 0000000..1e158f6 --- /dev/null +++ b/4.html @@ -0,0 +1,678 @@ + + + + + + + 4 + + + + + + + + + + + + + +
+
+ + +
+

Функциональное программирование

+
+
+ +

Напоминалочка

+
class Functor f where
+  fmap :: (a -> b) -> f a -> f b
+
class Functor f => Applicative f where
+  (<*>) :: f (a -> b) -> f a -> f b
+  pure :: a -> f a
+
class Applicative f => Monad f where
+  (>>=) :: f a -> (a -> f b) -> f b
+  return :: a -> f a
+
+
+ +

Either

+
data Either a b = Left a | Right b
+
throwError :: e -> Either e a
+throwError = Left
+
data PaymentError = InsufficientBalance
+
+pay :: Balance -> Price -> Either PaymentError Balance
+pay balance price = do
+  let newBalance = balance - price
+  when (newBalance < 0) (throwError InsufficientBalance)
+  return newBalance
+
when :: (Applicative f) => Bool -> f () -> f ()
+when p s  = if p then s else pure ()
+
+
+

State

+
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))
+
+
+ +
getNewId :: State TransactionId TransactionId
+getNewId = do
+  newId <- get
+  put (succ newId)
+  return newId
+
+data Transaction = Transaction
+  { transactionId :: TransactionId,
+    transactionAmount :: Price
+  }
+
registerPayment :: Price -> State TransactionId Transaction
+registerPayment price = do
+  newId <- getNewId
+  return Transaction
+    { transactionId = newId
+      transactionAmount = price
+    }
+
+
+ +
registerPayment :: Price -> State TransactionId Transaction
+pay :: Balance -> Price -> Either PaymentError Balance
+
payAndRegister balance price = do
+  newBalance <- pay balance price
+  transacation <- registerPayment price
+  return (newBalance, transaction)
+
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
+
-- Такого нет
+(>=>)
+  :: (Monad m, Monad n)
+  => (a -> m b) -> (b -> n c) -> (a -> ? c)
+

На каждый чих нужно создавать новый тип?

+
+
+ +

Мы хотим:

+
    +
  1. Отделить эффекты от конкретной монады
  2. +
  3. Получить возможность комбинировать разные эффекты
  4. +
+

(И чтобы не надо было на каждый случай новый тип создавать)

+
+
+ +
registerPayment :: Price -> State TransactionId Transaction
+pay :: Balance -> Price -> Either PaymentError Balance
+
registerPayment
+  :: MonadState TransactionId m
+  => Price -> m Transaction
+pay
+  :: MonadError PaymentError m
+  => Balance -> Price -> m Balance
+
payAndRegister
+  :: (MonadState TransactionId m, MonadError PaymentError m)
+  => Balance -> Price -> m (Balance, Transaction)
+
+
+ +

На самом деле мы хотим чтобы понятие “state” не было привязано к конкретной монаде.

+

Мы хотим чтобы “базовые операции” не были привязаны к конкретной монаде.

+
-- Было
+get :: State s s
+put :: s -> State s ()
+
-- Стало
+get :: MonadState s m => m s
+put :: MonadState s m => s -> m ()
+
+
+ +

MonadState

+
+
+ + +
class Monad m => MonadState s m where
+  get :: m s
+  put :: s -> m ()
+
data State s a = State { runState :: s -> (a, s) }
+
+instance MonadState s (State s) where
+  get = State (\s -> (s, s))
+  put s = State (\_ -> ((), s))
+
getNewId :: MonadState TransactionId m => m TransactionId
+getNewId = do
+  newId <- get
+  put (succ newId)
+  return newId
+
registerPayment :: Price -> State TransactionId Transaction
+registerPayment price = do
+  newId <- getNewId
+  return Transaction
+    { transactionId = newId
+      transactionAmount = price
+    }
+
+
+ +

MonadError

+
class Monad m => MonadError e m where
+  throwError :: e -> m a
+  catchError :: m a -> (e -> m a) -> m a
+
data Either e a = Left e | Right a
+
+instance MonadError e (Either e) where
+  throwError e = Left e
+
+  catchError (Left e) f = f e
+  catchError (Right a) _ = Right a
+
+
+ +

Мы хотим:

+
    +
  1. Отделить эффекты от конкретной монады
  2. +
  3. Получить возможность комбинировать разные эффекты
  4. +
+
+
+ +

Начнем с функторов

+
data Compose f g a = Compose { getCompose :: f (g a) }
+
type ErrorStateFunctor a =
+  Compose (Either PaymentError) (State TransactionId) a
+
-- Compose
+--   { getCompose ::
+--       Either PaymentError (State TransactionId a)
+--   }
+
instance (Functor f, Functor g) =>Functor (Compose f g) where
+  fmap f (Compose x) = Compose (fmap (fmap f) x)
+

🎉

+

(Applicative тоже можно – можете сами попробовать)

+
+
+ +

К монадам!

+
instance (Monad f, Monad g) => Monad (Compose f g) where
+  return x = Compose (return (return x))
+  (Compose x) >>= f = ???
+

😢

+

Все равно хочется!

+
+
+ +
data Compose f g a = Compose { getCompose :: f (g a) }
+

Для такого не можем

+
data ErrorState e s a =
+  ErrorState { runErrorState :: State s (Either e a) }
+

А для такого можем!

+
(State s (Either e a)) ~ (s -> Either e (a, s))
+
+
+ +
data ErrorState e s a =
+  ErrorState { runErrorState :: State s (Either e a) }
+

Хочется чтобы можно было не только Either!

+
data State  s   a = State  { runState  :: s ->   (a, s) }
+
data StateT s m a = StateT { runStateT :: s -> m (a, s) }
+
instance (Monad m) => Monad (StateT s m) where
+  return a = StateT $ \ s -> return (a, s)
+
+  m >>= f = StateT (\s -> do
+    (a, s') <- runStateT m s
+    runStateT (f a) s'
+    )
+
+
+ +

Transformers!

+

+
+
+ +

Вернем операции!

+
class Monad m => MonadState s m where
+  get :: m s
+  put :: s -> m ()
+
data StateT s n a = StateT { runStateT :: s -> n (a, s) }
+
instance Monad n => MonadState s (StateT s n) where
+  get = StateT (\s -> return (s, s))
+  put s = StateT (\_ -> return ((), s)
+
+
+ +

Error

+
data ExceptT e n a = ExceptT { runExceptT :: n (Either e a) }
+
class Monad m => MonadError e m where
+  throwError :: e -> m a
+  catchError :: m a -> (e -> m a) -> m a
+
instance Monad n => MonadError e (ExceptT e n) where
+  throwError e = ExceptT (return (Left e))
+
+  catchError (ExceptT n) f = ExceptT $ do
+    x <- n
+    case x of
+      Left e -> runExceptT (f e)
+      Right a -> return (Right a)
+
+
+ +
class Monad m => MonadError e m where
+  throwError :: e -> m a
+  catchError :: m a -> (e -> m a) -> m a
+
data PaymentError = InsufficientBalance
+
+pay :: MonadError e m => Balance -> Price -> m Balance
+pay balance price = do
+  let newBalance = balance - price
+  when (newBalance < 0) (throwError InsufficientBalance)
+  return newBalance
+
+
+ +

The states

+
data State  s   a = State  { runState  :: s ->   (a, s) }
+
data StateT s m a = StateT { runStateT :: s -> m (a, s) }
+

🤔

+
+
+ +

Напоминалочка:

+
data Identity a = Identity { runIdentity :: a }
+
(Identity a) ~ a
+
data State  s   a = State  { runState  :: s ->   (a, s) }
+
+data StateT s m a = StateT { runStateT :: s -> m (a, s) }
+
(StateT s Identity a) ~ (s -> Identity (a, s))
+

🤔

+
(s -> Identity (a, s)) ~ (s -> (a, s)) ~ (State s a)
+
type State s a = StateT s Identity a
+
+
+ +
data ExceptT e n a = ExceptT { runExceptT :: n (Either e a) }
+
+data StateT s m a = StateT { runStateT :: s -> m (a, s) }
+
+class Monad m => MonadError e m where
+  throwError :: e -> m a
+  catchError :: m a -> (e -> m a) -> m a
+
+class Monad m => MonadState s m where
+  get :: m s
+  put :: s -> m ()
+

Пришли к тому, с чего начинали, только еще сложнее

+
+
+ +
    +
  1. Отделить эффекты от конкретной монады
  2. +
  3. Получить возможность комбинировать разные эффекты
  4. +
+
+
+ +
foo :: ExceptT Bool (State Char) Int
+foo = do
+  c <- get -- :: State Char Char
+  throwError True -- :: ExceptT Bool (State Char) Int
+
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
+
+-- Такого нет
+(>=>)
+  :: (Monad m, Monad n)
+  => (a -> m b) -> (b -> n c) -> (a -> ? c)
+
lift :: State Char a -> ExceptT Bool (State Char) a
+
foo :: ExceptT Bool (State Char) Int
+foo = do
+  c <- lift get -- :: ExceptT Bool (State Char) Int
+  throwError True -- :: ExceptT Bool (State Char) Int
+
+
+ +
class MonadTrans t where
+    lift :: (Monad m) => m a -> t m a
+
lift :: State Char a -> ExceptT Bool (State Char) a
+--      (m       ) a -> (t         ) (m         ) a
+
instance MonadTrans (StateT s) where
+  lift m = StateT $ \ s -> do
+    a <- m
+    return (a, s)
+
instance MonadTrans (ExceptT e) where
+  lift m = ExceptT (fmap Right m)
+
foo :: ExceptT Bool (State Char) Int
+foo = do
+  c <- lift get
+  throwError True
+
+
+ +

Но хочется чтобы без lift 🙂

+
foo :: ExceptT Bool (State Char) Int
+foo = do
+  c <- get
+  throwError True
+

Нужен инстанс MonadState для ExceptT

+
instance MonadState s n => MonadState s (ExceptT e n) where
+    get = lift get
+    put s = lift (put s)
+
+
+ +
registerPayment
+  :: State TransactionId m
+  => Price -> m Transaction
+pay
+  :: MonadError PaymentError m
+  => Balance -> Price -> m Balance
+
payAndRegister
+  :: (State TransactionId m, MonadError PaymentError m)
+  => Balance -> Price -> m (Balance, Transaction)
+payAndRegister balance price = do
+  newBalance <- pay balance price
+  transacation <- registerPayment price
+  return (newBalance, transaction)
+
+
+ +

Как это “запускать”?

+
payAndRegister
+  :: (State TransactionId m, MonadError PaymentError m)
+  => Balance -> Price -> m (Balance, Transaction)
+
myBalance :: Balance
+myPrice :: Price
+
+balanceAndTransaction
+  :: Either PaymentError (Balance, Transaction)
+
data ExceptT e n a = ExceptT { runExceptT :: n (Either e a) }
+data StateT s n a = StateT { runStateT :: s -> n (a, s) }
+
balanceAndTransaction =
+  runStateT 0 (payAndRegister myBalance myPrice)
+
+
+ +
payAndRegister
+  :: (State TransactionId m, MonadError PaymentError m)
+  => Balance -> Price -> m (Balance, Transaction)
+
+data StateT s n a = StateT { runStateT :: s -> n (a, s) }
+
balanceAndTransaction
+  :: Either PaymentError (Balance, Transaction)
+balanceAndTransaction =
+  flip runStateT 0 (payAndRegister myBalance myPrice)
+
  flip runStateT 0 (payAndRegister myBalance myPrice)
+-- ^-------------^
+-- StateT TransactionId n a -> n a
+
-- (n a) ~ (Either PaymentError (Balance, Transaction))
+
flip runStateT 0 (payAndRegister myBalance myPrice)
+--               ^--------------------------------^
+--                    StateT TransactionId
+--                      (Either PaymentError)
+--                      (Balance, Transaction)
+
+
+ +
payAndRegister
+  :: (State TransactionId m, MonadError PaymentError m)
+  => Balance -> Price -> m (Balance, Transaction)
+
+data ExceptT e n a = ExceptT { runExceptT :: n (Either e a) }
+data StateT s n a = StateT { runStateT :: s -> n (a, s) }
+data Identity a = Identity { runIdentity :: a }
+
balanceAndTransaction
+  :: Either PaymentError (Balance, Transaction)
+balanceAndTransaction =
+  (runIdentity . runExceptT . flip runStateT 0)
+    (payAndRegister myBalance myPrice)
+
+
+ +

А теперь мы пойдем в совершенно другом направлении!

+
+
+ +

Напоминалочка

+
class Show a where
+  show :: a -> String
+
data Foo = Bar { barInt :: Int }
+  deriving Show
+
show (Bar 8)
+-- Bar {barInt = 8}
+
+
+ +

Read

+

Обратная операция к show

+
read :: Read a => String -> a
+
class Read a where
+  readsPrec
+    :: -- | Приоритет контекста выражения
+       Int
+    -> String
+    -> [(a, String)]
+
data Foo = Bar { barInt :: Int }
+  deriving (Show, Read)
+
read "Bar {barInt = 8}"
+-- Bar { barInt = 8 }
+
+
+

Монады

+
    +
  1. Базовые операции монады (эффекты)
  2. +
+

MonadState, MonadErrror

+
    +
  1. Конкретные монады (переносчик) (и способы их “разворачивать”)
  2. +
+

StateT (runStateT), Either, ExceptT (runExceptT)

+

Переводят эффекты в pure код

+
+
+

Монада IO

+

Обладает только базовыми операциями (Невозможно перевести в pure код)

+
getLine :: IO String
+
+putStrLn :: String -> IO ()
+
type FilePath = String
+
+readFile :: FilePath -> IO String
+
+writeFile :: FilePath -> String -> IO ()
+
+
+ +
add10FromConsole :: IO ()
+add10FromConsole = do
+  x <- getLine
+  let
+    n :: Int
+    n = read x
+  putStrLn (show (n + 10))
+
+
+ +

“Разворачивать” IO умеет только рантайм.

+
main :: IO ()
+main = add10FromConsole
+
> 10
+20
+
+
+ +
-- Как 'read', но не взрывается в рантайме
+readMaybe :: Read a => String -> Maybe a
+
accumulateNums :: StateT Int IO ()
+accumulateNums = do
+  x <- lift getLine
+  case (readMaybe x :: Int) of
+    Nothing -> do
+      s <- get
+      lift (putStrLn (show s))
+    Just x' -> do
+      modify (+ x')
+      accumulateNums
+
main :: IO ()
+main = flip runStateT 0 accumulateNums
+
> 8
+> 3
+> a
+11
+
+
+ +
accumulateNums :: StateT Int IO ()
+
-- Хочется так, но где тут взять 'IO'?
+accumulateNums :: MonadState Int m => m ()
+

Трансформера IO нет.

+

Тогда lift!

+

Но lift поднимает строго на один уровень.

+
+
+ +
class MonadIO m where
+  liftIO :: IO a -> m a
+
accumulateNums :: (MonadState Int m, MonadIO m) => m ()
+accumulateNums = do
+  x <- liftIO getLine
+  case (readMaybe x :: Int) of
+    Nothing -> do
+      s <- get
+      liftIO (putStrLn (show s))
+    Just x' -> do
+      modify (+ x')
+      accumulateNums
+
main :: IO ()
+main = flip runStateT 0 accumulateNums
+
> 8
+> 3
+> a
+11
+
+
+
+ + + + + + diff --git a/4.md b/4.md new file mode 100644 index 0000000..a51e7d3 --- /dev/null +++ b/4.md @@ -0,0 +1,857 @@ +--- +theme: superblack +author: Ilya Kostyuchenko +--- + +# Функциональное программирование + +--- + +## Напоминалочка + +```{ .haskell } +class Functor f where + fmap :: (a -> b) -> f a -> f b +``` + +```{ .haskell .fragment } +class Functor f => Applicative f where + (<*>) :: f (a -> b) -> f a -> f b + pure :: a -> f a +``` + +```{ .haskell .fragment } +class Applicative f => Monad f where + (>>=) :: f a -> (a -> f b) -> f b + return :: a -> f a +``` + +--- + +## Either + +```{ .haskell } +data Either a b = Left a | Right b +``` + +```{ .haskell .fragment } +throwError :: e -> Either e a +throwError = Left +``` + +```{ .haskell .fragment } +data PaymentError = InsufficientBalance + +pay :: Balance -> Price -> Either PaymentError Balance +pay balance price = do + let newBalance = balance - price + when (newBalance < 0) (throwError InsufficientBalance) + return newBalance +``` + +```{ .haskell .fragment } +when :: (Applicative f) => Bool -> f () -> f () +when p s = if p then s else pure () +``` + +--- + +# State + +```{ .haskell } +data State s a = State { runState :: s -> (a, s) } +``` + +```{ .haskell .fragment } +get :: State s s +get = State (\s -> (s, s)) + +put :: s -> State s () +put s = State (\_ -> (_, s)) +``` + +--- + +```{ .haskell } +getNewId :: State TransactionId TransactionId +getNewId = do + newId <- get + put (succ newId) + return newId + +data Transaction = Transaction + { transactionId :: TransactionId, + transactionAmount :: Price + } +``` + +```{ .haskell .fragment } +registerPayment :: Price -> State TransactionId Transaction +registerPayment price = do + newId <- getNewId + return Transaction + { transactionId = newId + transactionAmount = price + } +``` + +--- + +```{ .haskell } +registerPayment :: Price -> State TransactionId Transaction +pay :: Balance -> Price -> Either PaymentError Balance +``` + +```{ .haskell .fragment } +payAndRegister balance price = do + newBalance <- pay balance price + transacation <- registerPayment price + return (newBalance, transaction) +``` + +```{ .haskell .fragment } +(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c) +``` + +```{ .haskell .fragment } +-- Такого нет +(>=>) + :: (Monad m, Monad n) + => (a -> m b) -> (b -> n c) -> (a -> ? c) +``` + +[На каждый чих нужно создавать новый тип?]{.fragment} + +--- + +Мы хотим: + +1. Отделить эффекты от конкретной монады +2. Получить возможность комбинировать разные эффекты + +[(И чтобы не надо было на каждый случай новый тип создавать)]{.fragment} + +--- + +```{ .haskell } +registerPayment :: Price -> State TransactionId Transaction +pay :: Balance -> Price -> Either PaymentError Balance +``` + +```{ .haskell .fragment } +registerPayment + :: MonadState TransactionId m + => Price -> m Transaction +pay + :: MonadError PaymentError m + => Balance -> Price -> m Balance +``` + +```{ .haskell .fragment } +payAndRegister + :: (MonadState TransactionId m, MonadError PaymentError m) + => Balance -> Price -> m (Balance, Transaction) +``` + +--- + +На самом деле мы хотим чтобы понятие "state" не было привязано к конкретной монаде. + +[Мы хотим чтобы "базовые операции" не были привязаны к конкретной монаде.]{.fragment} + +```{ .haskell .fragment } +-- Было +get :: State s s +put :: s -> State s () +``` + +```{ .haskell .fragment } +-- Стало +get :: MonadState s m => m s +put :: MonadState s m => s -> m () +``` + +--- + +## MonadState + +--- + + +```{ .haskell } +class Monad m => MonadState s m where + get :: m s + put :: s -> m () +``` + +```{ .haskell .fragment } +data State s a = State { runState :: s -> (a, s) } + +instance MonadState s (State s) where + get = State (\s -> (s, s)) + put s = State (\_ -> ((), s)) +``` + +```{ .haskell .fragment } +getNewId :: MonadState TransactionId m => m TransactionId +getNewId = do + newId <- get + put (succ newId) + return newId +``` + +```{ .haskell .fragment } +registerPayment :: Price -> State TransactionId Transaction +registerPayment price = do + newId <- getNewId + return Transaction + { transactionId = newId + transactionAmount = price + } +``` + +--- + +## MonadError + +```{ .haskell .fragment } +class Monad m => MonadError e m where + throwError :: e -> m a + catchError :: m a -> (e -> m a) -> m a +``` + +```{ .haskell .fragment } +data Either e a = Left e | Right a + +instance MonadError e (Either e) where + throwError e = Left e + + catchError (Left e) f = f e + catchError (Right a) _ = Right a +``` + +--- + +Мы хотим: + +1. ~~Отделить эффекты от конкретной монады~~ +2. Получить возможность комбинировать разные эффекты + +--- + +Начнем с функторов + +```{ .haskell .fragment } +data Compose f g a = Compose { getCompose :: f (g a) } +``` + +```{ .haskell .fragment } +type ErrorStateFunctor a = + Compose (Either PaymentError) (State TransactionId) a +``` + +```{ .haskell .fragment } +-- Compose +-- { getCompose :: +-- Either PaymentError (State TransactionId a) +-- } +``` + +```{ .haskell .fragment } +instance (Functor f, Functor g) =>Functor (Compose f g) where + fmap f (Compose x) = Compose (fmap (fmap f) x) +``` + +[🎉]{ .fragment } + +[(`Applicative` тоже можно -- можете сами попробовать)]{ .fragment } + +--- + +К монадам! + +```{ .haskell .fragment } +instance (Monad f, Monad g) => Monad (Compose f g) where + return x = Compose (return (return x)) + (Compose x) >>= f = ??? +``` + +[😢]{ .fragment } + +[Все равно хочется!]{ .fragment } + +--- + + +```{ .haskell } +data Compose f g a = Compose { getCompose :: f (g a) } +``` + +[Для такого не можем]{ .fragment } + + +```{ .haskell .fragment } +data ErrorState e s a = + ErrorState { runErrorState :: State s (Either e a) } +``` + +[А для такого можем!]{ .fragment } + +```{ .haskell .fragment } +(State s (Either e a)) ~ (s -> Either e (a, s)) +``` + +--- + +```{ .haskell } +data ErrorState e s a = + ErrorState { runErrorState :: State s (Either e a) } +``` + +[Хочется чтобы можно было не только `Either`!]{ .fragment } + +```{ .haskell .fragment } +data State s a = State { runState :: s -> (a, s) } +``` + +```{ .haskell .fragment } +data StateT s m a = StateT { runStateT :: s -> m (a, s) } +``` + +```{ .haskell .fragment } +instance (Monad m) => Monad (StateT s m) where + return a = StateT $ \ s -> return (a, s) + + m >>= f = StateT (\s -> do + (a, s') <- runStateT m s + runStateT (f a) s' + ) +``` + + +--- + +## Transformers! + +![](images/transformer.gif){height=400px} + +--- + +## Вернем операции! + +```{ .haskell } +class Monad m => MonadState s m where + get :: m s + put :: s -> m () +``` + +```{ .haskell .fragment } +data StateT s n a = StateT { runStateT :: s -> n (a, s) } +``` + +```{ .haskell .fragment } +instance Monad n => MonadState s (StateT s n) where + get = StateT (\s -> return (s, s)) + put s = StateT (\_ -> return ((), s) +``` + +--- + +## Error + +```{ .haskell .fragment } +data ExceptT e n a = ExceptT { runExceptT :: n (Either e a) } +``` + +```{ .haskell .fragment } +class Monad m => MonadError e m where + throwError :: e -> m a + catchError :: m a -> (e -> m a) -> m a +``` + +```{ .haskell .fragment } +instance Monad n => MonadError e (ExceptT e n) where + throwError e = ExceptT (return (Left e)) + + catchError (ExceptT n) f = ExceptT $ do + x <- n + case x of + Left e -> runExceptT (f e) + Right a -> return (Right a) +``` + +--- + +```{ .haskell } +class Monad m => MonadError e m where + throwError :: e -> m a + catchError :: m a -> (e -> m a) -> m a +``` + +```{ .haskell .fragment } +data PaymentError = InsufficientBalance + +pay :: MonadError e m => Balance -> Price -> m Balance +pay balance price = do + let newBalance = balance - price + when (newBalance < 0) (throwError InsufficientBalance) + return newBalance +``` + +--- + +## The states + + +```{ .haskell .fragment } +data State s a = State { runState :: s -> (a, s) } +``` + +```{ .haskell .fragment } +data StateT s m a = StateT { runStateT :: s -> m (a, s) } +``` + +[🤔]{.fragment} + +--- + +Напоминалочка: + +```{ .haskell .fragment } +data Identity a = Identity { runIdentity :: a } +``` + +```{ .haskell .fragment } +(Identity a) ~ a +``` + +```{ .haskell .fragment } +data State s a = State { runState :: s -> (a, s) } + +data StateT s m a = StateT { runStateT :: s -> m (a, s) } +``` + +```{ .haskell .fragment } +(StateT s Identity a) ~ (s -> Identity (a, s)) +``` + +[🤔]{.fragment} + +```{ .haskell .fragment } +(s -> Identity (a, s)) ~ (s -> (a, s)) ~ (State s a) +``` + +```{ .haskell .fragment } +type State s a = StateT s Identity a +``` + +--- + +```{ .haskell } +data ExceptT e n a = ExceptT { runExceptT :: n (Either e a) } + +data StateT s m a = StateT { runStateT :: s -> m (a, s) } + +class Monad m => MonadError e m where + throwError :: e -> m a + catchError :: m a -> (e -> m a) -> m a + +class Monad m => MonadState s m where + get :: m s + put :: s -> m () +``` + +Пришли к тому, с чего начинали, только еще сложнее + +--- + +1. ~~Отделить эффекты от конкретной монады~~ +2. Получить возможность комбинировать разные эффекты + +--- + +```{ .haskell } +foo :: ExceptT Bool (State Char) Int +foo = do + c <- get -- :: State Char Char + throwError True -- :: ExceptT Bool (State Char) Int +``` + +```{ .haskell .fragment } +(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c) + +-- Такого нет +(>=>) + :: (Monad m, Monad n) + => (a -> m b) -> (b -> n c) -> (a -> ? c) +``` + +```{ .haskell .fragment } +lift :: State Char a -> ExceptT Bool (State Char) a +``` + +```{ .haskell .fragment } +foo :: ExceptT Bool (State Char) Int +foo = do + c <- lift get -- :: ExceptT Bool (State Char) Int + throwError True -- :: ExceptT Bool (State Char) Int +``` + +--- + +```{ .haskell } +class MonadTrans t where + lift :: (Monad m) => m a -> t m a +``` + +```{ .haskell .fragment } +lift :: State Char a -> ExceptT Bool (State Char) a +-- (m ) a -> (t ) (m ) a +``` + +```{ .haskell .fragment } +instance MonadTrans (StateT s) where + lift m = StateT $ \ s -> do + a <- m + return (a, s) +``` + +```{ .haskell .fragment } +instance MonadTrans (ExceptT e) where + lift m = ExceptT (fmap Right m) +``` + +```{ .haskell .fragment } +foo :: ExceptT Bool (State Char) Int +foo = do + c <- lift get + throwError True +``` + +--- + +Но хочется чтобы без `lift` 🙂 + +```{ .haskell .fragment } +foo :: ExceptT Bool (State Char) Int +foo = do + c <- get + throwError True +``` + +[Нужен инстанс `MonadState` для `ExceptT`]{ .fragment } + +```{ .haskell .fragment } +instance MonadState s n => MonadState s (ExceptT e n) where + get = lift get + put s = lift (put s) +``` + +--- + + +```{ .haskell } +registerPayment + :: State TransactionId m + => Price -> m Transaction +pay + :: MonadError PaymentError m + => Balance -> Price -> m Balance +``` + +```{ .haskell .fragment } +payAndRegister + :: (State TransactionId m, MonadError PaymentError m) + => Balance -> Price -> m (Balance, Transaction) +payAndRegister balance price = do + newBalance <- pay balance price + transacation <- registerPayment price + return (newBalance, transaction) +``` + +--- + +Как это "запускать"? + +```{ .haskell } +payAndRegister + :: (State TransactionId m, MonadError PaymentError m) + => Balance -> Price -> m (Balance, Transaction) +``` + +```{ .haskell .fragment } +myBalance :: Balance +myPrice :: Price + +balanceAndTransaction + :: Either PaymentError (Balance, Transaction) +``` + +```{ .haskell .fragment } +data ExceptT e n a = ExceptT { runExceptT :: n (Either e a) } +data StateT s n a = StateT { runStateT :: s -> n (a, s) } +``` + + +```{ .haskell .fragment } +balanceAndTransaction = + runStateT 0 (payAndRegister myBalance myPrice) +``` + +--- + +```{ .haskell } +payAndRegister + :: (State TransactionId m, MonadError PaymentError m) + => Balance -> Price -> m (Balance, Transaction) + +data StateT s n a = StateT { runStateT :: s -> n (a, s) } +``` + +```{ .haskell .fragment } +balanceAndTransaction + :: Either PaymentError (Balance, Transaction) +balanceAndTransaction = + flip runStateT 0 (payAndRegister myBalance myPrice) +``` + +```{ .haskell .fragment } + flip runStateT 0 (payAndRegister myBalance myPrice) +-- ^-------------^ +-- StateT TransactionId n a -> n a +``` + +```{ .haskell .fragment } +-- (n a) ~ (Either PaymentError (Balance, Transaction)) +``` + +```{ .haskell .fragment } +flip runStateT 0 (payAndRegister myBalance myPrice) +-- ^--------------------------------^ +-- StateT TransactionId +-- (Either PaymentError) +-- (Balance, Transaction) +``` + +--- + +```{ .haskell } +payAndRegister + :: (State TransactionId m, MonadError PaymentError m) + => Balance -> Price -> m (Balance, Transaction) + +data ExceptT e n a = ExceptT { runExceptT :: n (Either e a) } +data StateT s n a = StateT { runStateT :: s -> n (a, s) } +data Identity a = Identity { runIdentity :: a } +``` + +```{ .haskell .fragment } +balanceAndTransaction + :: Either PaymentError (Balance, Transaction) +balanceAndTransaction = + (runIdentity . runExceptT . flip runStateT 0) + (payAndRegister myBalance myPrice) +``` + +--- + +А теперь мы пойдем в совершенно другом направлении! + +--- + +## Напоминалочка + +```{ .haskell .fragment } +class Show a where + show :: a -> String +``` + +```{ .haskell .fragment } +data Foo = Bar { barInt :: Int } + deriving Show +``` + +```{ .haskell .fragment } +show (Bar 8) +-- Bar {barInt = 8} +``` + +--- + +## Read + +Обратная операция к `show` + +```{ .haskell } +read :: Read a => String -> a +``` + +```{ .haskell .fragment } +class Read a where + readsPrec + :: -- | Приоритет контекста выражения + Int + -> String + -> [(a, String)] +``` + +```{ .haskell .fragment } +data Foo = Bar { barInt :: Int } + deriving (Show, Read) +``` + +```{ .haskell .fragment } +read "Bar {barInt = 8}" +-- Bar { barInt = 8 } +``` + +--- + +# Монады + +1. Базовые операции монады (эффекты) + +[`MonadState`, `MonadErrror`]{ .fragment } + +2. Конкретные монады (переносчик) (и способы их "разворачивать") + +[`StateT` (`runStateT`), `Either`, `ExceptT` (`runExceptT`)]{ .fragment } + +[Переводят эффекты в pure код]{ .fragment } + +--- + +# Монада `IO` + +[Обладает только базовыми операциями]{ .fragment } +[(Невозможно перевести в pure код)]{ .fragment } + +```{ .haskell .fragment } +getLine :: IO String + +putStrLn :: String -> IO () +``` + +```{ .haskell .fragment } +type FilePath = String + +readFile :: FilePath -> IO String + +writeFile :: FilePath -> String -> IO () +``` + +--- + +```{ .haskell } +add10FromConsole :: IO () +add10FromConsole = do + x <- getLine + let + n :: Int + n = read x + putStrLn (show (n + 10)) +``` + +--- + +"Разворачивать" `IO` умеет только рантайм. + +```{ .haskell .fragment } +main :: IO () +main = add10FromConsole +``` + +```{ .fragment } +> 10 +20 +``` + +--- + +```{ .haskell } +-- Как 'read', но не взрывается в рантайме +readMaybe :: Read a => String -> Maybe a +``` + +```{ .haskell .fragment } +accumulateNums :: StateT Int IO () +accumulateNums = do + x <- lift getLine + case (readMaybe x :: Int) of + Nothing -> do + s <- get + lift (putStrLn (show s)) + Just x' -> do + modify (+ x') + accumulateNums +``` + +```{ .haskell .fragment } +main :: IO () +main = flip runStateT 0 accumulateNums +``` + +```{ .fragment } +> 8 +> 3 +> a +11 +``` + +--- + +```{ .haskell } +accumulateNums :: StateT Int IO () +``` + +```{ .haskell .fragment } +-- Хочется так, но где тут взять 'IO'? +accumulateNums :: MonadState Int m => m () +``` + +[Трансформера `IO` нет.]{ .fragment } + +[Тогда `lift`!]{ .fragment } + +[Но `lift` поднимает строго на один уровень.]{ .fragment } + +--- + +```{ .haskell } +class MonadIO m where + liftIO :: IO a -> m a +``` + +```{ .haskell .fragment } +accumulateNums :: (MonadState Int m, MonadIO m) => m () +accumulateNums = do + x <- liftIO getLine + case (readMaybe x :: Int) of + Nothing -> do + s <- get + liftIO (putStrLn (show s)) + Just x' -> do + modify (+ x') + accumulateNums +``` + +```{ .haskell .fragment } +main :: IO () +main = flip runStateT 0 accumulateNums +``` + +```{ .fragment } +> 8 +> 3 +> a +11 +``` diff --git a/README.md b/README.md index 064e647..c51513b 100644 --- a/README.md +++ b/README.md @@ -34,3 +34,14 @@ - [`mapM`](https://hackage.haskell.org/package/base-4.12.0.0/docs/Control-Monad.html#v:mapM) - [`State`](https://wiki.haskell.org/State_Monad) - [`Applicative`](http://learnyouahaskell.com/functors-applicative-functors-and-monoids#applicative-functors) +- Lecture 4 [[md](https://github.com/ikoHSE/sc-lectures/blob/master/4.md)] [[web](https://ikohse.github.io/sc-lectures/4.html)] + - [Transformers](https://mmhaskell.com/monads/transformers) + - [`MonadState`](https://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Monad-State-Class.html#t:MonadState) + - [`MonadError`](https://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Monad-Except.html#t:MonadError) + - [`Compose`](https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-Functor-Compose.html#t:Compose) + - [`StateT`](https://hackage.haskell.org/package/transformers-0.5.6.2/docs/Control-Monad-Trans-State-Lazy.html#t:StateT) + - [`ExceptT`](https://hackage.haskell.org/package/transformers-0.5.6.2/docs/Control-Monad-Trans-Except.html#t:ExceptT) + - [`MonadTrans`](https://hackage.haskell.org/package/transformers-0.5.6.2/docs/Control-Monad-Trans-Class.html#t:MonadTrans) + - [`Read`](https://hackage.haskell.org/package/base-4.14.0.0/docs/Prelude.html#t:Read) + - [`IO`](https://www.haskell.org/tutorial/io.html) + - [`MonadIO`](https://hackage.haskell.org/package/base-4.14.0.0/docs/Control-Monad-IO-Class.html#t:MonadIO) diff --git a/images/transformer.gif b/images/transformer.gif new file mode 100644 index 0000000..99565ae Binary files /dev/null and b/images/transformer.gif differ