+
+
+
+Функциональное программирование
+
+
+
+Напоминалочка
+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)
+На каждый чих нужно создавать новый тип?
+
+
+
+Мы хотим:
+
+- Отделить эффекты от конкретной монады
+- Получить возможность комбинировать разные эффекты
+
+(И чтобы не надо было на каждый случай новый тип создавать)
+
+
+
+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 ()
+
+
+
+
+
+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
+
+
+
+Мы хотим:
+
+Отделить эффекты от конкретной монады
+- Получить возможность комбинировать разные эффекты
+
+
+
+
+Начнем с функторов
+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'
+ )
+
+
+
+
+![]()
+
+
+
+Вернем операции!
+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 }
+
+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 ()
+Пришли к тому, с чего начинали, только еще сложнее
+
+
+
+
+Отделить эффекты от конкретной монады
+- Получить возможность комбинировать разные эффекты
+
+
+
+
+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 }
+
+
+Монады
+
+- Базовые операции монады (эффекты)
+
+MonadState
, MonadErrror
+
+- Конкретные монады (переносчик) (и способы их “разворачивать”)
+
+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
+
+