3author: Ilya Kostyuchenko
6# Функциональное программирование
14 fmap :: (a -> b) -> f a -> f b
17```{ .haskell .fragment }
18class Functor f => Applicative f where
19 (<*>) :: f (a -> b) -> f a -> f b
23```{ .haskell .fragment }
24class Applicative f => Monad f where
25 (>>=) :: f a -> (a -> f b) -> f b
34data Either a b = Left a | Right b
37```{ .haskell .fragment }
38throwError :: e -> Either e a
42```{ .haskell .fragment }
43data PaymentError = InsufficientBalance
45pay :: Balance -> Price -> Either PaymentError Balance
47 let newBalance = balance - price
48 when (newBalance < 0) (throwError InsufficientBalance)
52```{ .haskell .fragment }
53when :: (Applicative f) => Bool -> f () -> f ()
54when p s = if p then s else pure ()
62data State s a = State { runState :: s -> (a, s) }
65```{ .haskell .fragment }
67get = State (\s -> (s, s))
70put s = State (\_ -> (_, s))
76getNewId :: State TransactionId TransactionId
82data Transaction = Transaction
83 { transactionId :: TransactionId,
84 transactionAmount :: Price
88```{ .haskell .fragment }
89registerPayment :: Price -> State TransactionId Transaction
90registerPayment price = do
93 { transactionId = newId
94 transactionAmount = price
101registerPayment :: Price -> State TransactionId Transaction
102pay :: Balance -> Price -> Either PaymentError Balance
105```{ .haskell .fragment }
106payAndRegister balance price = do
107 newBalance <- pay balance price
108 transacation <- registerPayment price
109 return (newBalance, transaction)
112```{ .haskell .fragment }
113(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
116```{ .haskell .fragment }
119 :: (Monad m, Monad n)
120 => (a -> m b) -> (b -> n c) -> (a -> ? c)
123[На каждый чих нужно создавать новый тип?]{.fragment}
1291. Отделить эффекты от конкретной монады
1302. Получить возможность комбинировать разные эффекты
132[(И чтобы не надо было на каждый случай новый тип создавать)]{.fragment}
137registerPayment :: Price -> State TransactionId Transaction
138pay :: Balance -> Price -> Either PaymentError Balance
141```{ .haskell .fragment }
143 :: MonadState TransactionId m
144 => Price -> m Transaction
146 :: MonadError PaymentError m
147 => Balance -> Price -> m Balance
150```{ .haskell .fragment }
152 :: (MonadState TransactionId m, MonadError PaymentError m)
153 => Balance -> Price -> m (Balance, Transaction)
158На самом деле мы хотим чтобы понятие "state" не было привязано к конкретной монаде.
160[Мы хотим чтобы "базовые операции" не были привязаны к конкретной монаде.]{.fragment}
162```{ .haskell .fragment }
165put :: s -> State s ()
168```{ .haskell .fragment }
170get :: MonadState s m => m s
171put :: MonadState s m => s -> m ()
181```{ .haskell .fragment }
182class MonadState s m | m -> s where
187[`m -> s` означает что для каждого `m` может быть строго один `s`.]{ .fragment }
189[(Это нужно чтобы когда вы делаете `get` было понятно что вам возвращать.)]{ .fragment } -->
191class Monad m => MonadState s m where
196```{ .haskell .fragment }
197data State s a = State { runState :: s -> (a, s) }
199instance MonadState s (State s) where
200 get = State (\s -> (s, s))
201 put s = State (\_ -> ((), s))
204```{ .haskell .fragment }
205getNewId :: MonadState TransactionId m => m TransactionId
212```{ .haskell .fragment }
213registerPayment :: Price -> State TransactionId Transaction
214registerPayment price = do
217 { transactionId = newId
218 transactionAmount = price
226```{ .haskell .fragment }
227class Monad m => MonadError e m where
228 throwError :: e -> m a
229 catchError :: m a -> (e -> m a) -> m a
232```{ .haskell .fragment }
233data Either e a = Left e | Right a
235instance MonadError e (Either e) where
236 throwError e = Left e
238 catchError (Left e) f = f e
239 catchError (Right a) _ = Right a
2461. ~~Отделить эффекты от конкретной монады~~
2472. Получить возможность комбинировать разные эффекты
253```{ .haskell .fragment }
254data Compose f g a = Compose { getCompose :: f (g a) }
257```{ .haskell .fragment }
258type ErrorStateFunctor a =
259 Compose (Either PaymentError) (State TransactionId) a
262```{ .haskell .fragment }
265-- Either PaymentError (State TransactionId a)
269```{ .haskell .fragment }
270instance (Functor f, Functor g) =>Functor (Compose f g) where
271 fmap f (Compose x) = Compose (fmap (fmap f) x)
276[(`Applicative` тоже можно -- можете сами попробовать)]{ .fragment }
282```{ .haskell .fragment }
283instance (Monad f, Monad g) => Monad (Compose f g) where
284 return x = Compose (return (return x))
285 (Compose x) >>= f = ???
290[Все равно хочется!]{ .fragment }
296data Compose f g a = Compose { getCompose :: f (g a) }
299[Для такого не можем]{ .fragment }
302```{ .haskell .fragment }
303data ErrorState e s a =
304 ErrorState { runErrorState :: State s (Either e a) }
307[А для такого можем!]{ .fragment }
309```{ .haskell .fragment }
310(State s (Either e a)) ~ (s -> Either e (a, s))
316data ErrorState e s a =
317 ErrorState { runErrorState :: State s (Either e a) }
320[Хочется чтобы можно было не только `Either`!]{ .fragment }
322```{ .haskell .fragment }
323data State s a = State { runState :: s -> (a, s) }
326```{ .haskell .fragment }
327data StateT s m a = StateT { runStateT :: s -> m (a, s) }
330```{ .haskell .fragment }
331instance (Monad m) => Monad (StateT s m) where
332 return a = StateT $ \ s -> return (a, s)
334 m >>= f = StateT (\s -> do
335 (a, s') <- runStateT m s
345{height=400px}
352class Monad m => MonadState s m where
357```{ .haskell .fragment }
358data StateT s n a = StateT { runStateT :: s -> n (a, s) }
361```{ .haskell .fragment }
362instance Monad n => MonadState s (StateT s n) where
363 get = StateT (\s -> return (s, s))
364 put s = StateT (\_ -> return ((), s)
371```{ .haskell .fragment }
372data ExceptT e n a = ExceptT { runExceptT :: n (Either e a) }
375```{ .haskell .fragment }
376class Monad m => MonadError e m where
377 throwError :: e -> m a
378 catchError :: m a -> (e -> m a) -> m a
381```{ .haskell .fragment }
382instance Monad n => MonadError e (ExceptT e n) where
383 throwError e = ExceptT (return (Left e))
385 catchError (ExceptT n) f = ExceptT $ do
388 Left e -> runExceptT (f e)
389 Right a -> return (Right a)
395class Monad m => MonadError e m where
396 throwError :: e -> m a
397 catchError :: m a -> (e -> m a) -> m a
400```{ .haskell .fragment }
401data PaymentError = InsufficientBalance
403pay :: MonadError e m => Balance -> Price -> m Balance
404pay balance price = do
405 let newBalance = balance - price
406 when (newBalance < 0) (throwError InsufficientBalance)
415```{ .haskell .fragment }
416data State s a = State { runState :: s -> (a, s) }
419```{ .haskell .fragment }
420data StateT s m a = StateT { runStateT :: s -> m (a, s) }
429```{ .haskell .fragment }
430data Identity a = Identity { runIdentity :: a }
433```{ .haskell .fragment }
437```{ .haskell .fragment }
438data State s a = State { runState :: s -> (a, s) }
440data StateT s m a = StateT { runStateT :: s -> m (a, s) }
443```{ .haskell .fragment }
444(StateT s Identity a) ~ (s -> Identity (a, s))
449```{ .haskell .fragment }
450(s -> Identity (a, s)) ~ (s -> (a, s)) ~ (State s a)
453```{ .haskell .fragment }
454type State s a = StateT s Identity a
460data ExceptT e n a = ExceptT { runExceptT :: n (Either e a) }
462data StateT s m a = StateT { runStateT :: s -> m (a, s) }
464class Monad m => MonadError e m where
465 throwError :: e -> m a
466 catchError :: m a -> (e -> m a) -> m a
468class Monad m => MonadState s m where
473Пришли к тому, с чего начинали, только еще сложнее
4771. ~~Отделить эффекты от конкретной монады~~
4782. Получить возможность комбинировать разные эффекты
483foo :: ExceptT Bool (State Char) Int
485 c <- get -- :: State Char Char
486 throwError True -- :: ExceptT Bool (State Char) Int
489```{ .haskell .fragment }
490(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
494 :: (Monad m, Monad n)
495 => (a -> m b) -> (b -> n c) -> (a -> ? c)
498```{ .haskell .fragment }
499lift :: State Char a -> ExceptT Bool (State Char) a
502```{ .haskell .fragment }
503foo :: ExceptT Bool (State Char) Int
505 c <- lift get -- :: ExceptT Bool (State Char) Int
506 throwError True -- :: ExceptT Bool (State Char) Int
512class MonadTrans t where
513 lift :: (Monad m) => m a -> t m a
516```{ .haskell .fragment }
517lift :: State Char a -> ExceptT Bool (State Char) a
518-- (m ) a -> (t ) (m ) a
521```{ .haskell .fragment }
522instance MonadTrans (StateT s) where
523 lift m = StateT $ \ s -> do
528```{ .haskell .fragment }
529instance MonadTrans (ExceptT e) where
530 lift m = ExceptT (fmap Right m)
533```{ .haskell .fragment }
534foo :: ExceptT Bool (State Char) Int
542Но хочется чтобы без `lift` 🙂
544```{ .haskell .fragment }
545foo :: ExceptT Bool (State Char) Int
551[Нужен инстанс `MonadState` для `ExceptT`]{ .fragment }
553```{ .haskell .fragment }
554instance MonadState s n => MonadState s (ExceptT e n) where
564 :: State TransactionId m
565 => Price -> m Transaction
567 :: MonadError PaymentError m
568 => Balance -> Price -> m Balance
571```{ .haskell .fragment }
573 :: (State TransactionId m, MonadError PaymentError m)
574 => Balance -> Price -> m (Balance, Transaction)
575payAndRegister balance price = do
576 newBalance <- pay balance price
577 transacation <- registerPayment price
578 return (newBalance, transaction)
587 :: (State TransactionId m, MonadError PaymentError m)
588 => Balance -> Price -> m (Balance, Transaction)
591```{ .haskell .fragment }
596 :: Either PaymentError (Balance, Transaction)
599```{ .haskell .fragment }
600data ExceptT e n a = ExceptT { runExceptT :: n (Either e a) }
601data StateT s n a = StateT { runStateT :: s -> n (a, s) }
605```{ .haskell .fragment }
606balanceAndTransaction =
607 runStateT 0 (payAndRegister myBalance myPrice)
614 :: (State TransactionId m, MonadError PaymentError m)
615 => Balance -> Price -> m (Balance, Transaction)
617data StateT s n a = StateT { runStateT :: s -> n (a, s) }
620```{ .haskell .fragment }
622 :: Either PaymentError (Balance, Transaction)
623balanceAndTransaction =
624 flip runStateT 0 (payAndRegister myBalance myPrice)
627```{ .haskell .fragment }
628 flip runStateT 0 (payAndRegister myBalance myPrice)
630-- StateT TransactionId n a -> n a
633```{ .haskell .fragment }
634-- (n a) ~ (Either PaymentError (Balance, Transaction))
637```{ .haskell .fragment }
638flip runStateT 0 (payAndRegister myBalance myPrice)
639-- ^--------------------------------^
640-- StateT TransactionId
641-- (Either PaymentError)
642-- (Balance, Transaction)
649 :: (State TransactionId m, MonadError PaymentError m)
650 => Balance -> Price -> m (Balance, Transaction)
652data ExceptT e n a = ExceptT { runExceptT :: n (Either e a) }
653data StateT s n a = StateT { runStateT :: s -> n (a, s) }
654data Identity a = Identity { runIdentity :: a }
657```{ .haskell .fragment }
659 :: Either PaymentError (Balance, Transaction)
660balanceAndTransaction =
661 (runIdentity . runExceptT . flip runStateT 0)
662 (payAndRegister myBalance myPrice)
667А теперь мы пойдем в совершенно другом направлении!
673```{ .haskell .fragment }
678```{ .haskell .fragment }
679data Foo = Bar { barInt :: Int }
683```{ .haskell .fragment }
692Обратная операция к `show`
695read :: Read a => String -> a
698```{ .haskell .fragment }
701 :: -- | Приоритет контекста выражения
707```{ .haskell .fragment }
708data Foo = Bar { barInt :: Int }
709 deriving (Show, Read)
712```{ .haskell .fragment }
713read "Bar {barInt = 8}"
7211. Базовые операции монады (эффекты)
723[`MonadState`, `MonadErrror`]{ .fragment }
7252. Конкретные монады (переносчик) (и способы их "разворачивать")
727[`StateT` (`runStateT`), `Either`, `ExceptT` (`runExceptT`)]{ .fragment }
729[Переводят эффекты в pure код]{ .fragment }
735[Обладает только базовыми операциями]{ .fragment }
736[(Невозможно перевести в pure код)]{ .fragment }
738```{ .haskell .fragment }
741putStrLn :: String -> IO ()
744```{ .haskell .fragment }
745type FilePath = String
747readFile :: FilePath -> IO String
749writeFile :: FilePath -> String -> IO ()
755add10FromConsole :: IO ()
761 putStrLn (show (n + 10))
766"Разворачивать" `IO` умеет только рантайм.
768```{ .haskell .fragment }
770main = add10FromConsole
781-- Как 'read', но не взрывается в рантайме
782readMaybe :: Read a => String -> Maybe a
785```{ .haskell .fragment }
786accumulateNums :: StateT Int IO ()
789 case (readMaybe x :: Int) of
792 lift (putStrLn (show s))
798```{ .haskell .fragment }
800main = flip runStateT 0 accumulateNums
813accumulateNums :: StateT Int IO ()
816```{ .haskell .fragment }
817-- Хочется так, но где тут взять 'IO'?
818accumulateNums :: MonadState Int m => m ()
821[Трансформера `IO` нет.]{ .fragment }
823[Тогда `lift`!]{ .fragment }
825[Но `lift` поднимает строго на один уровень.]{ .fragment }
831 liftIO :: IO a -> m a
834```{ .haskell .fragment }
835accumulateNums :: (MonadState Int m, MonadIO m) => m ()
838 case (readMaybe x :: Int) of
841 liftIO (putStrLn (show s))
847```{ .haskell .fragment }
849main = flip runStateT 0 accumulateNums
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list