Функциональное программирование, весна 2016:...

44
Функциональное программирование Лекция 11. Трансформеры монад Денис Николаевич Москвин СПбАУ РАН 19.04.2016 Денис Николаевич Москвин Трансформеры монад

Transcript of Функциональное программирование, весна 2016:...

Page 1: Функциональное программирование, весна 2016: Трансформеры монад

Функциональное программированиеЛекция 11. Трансформеры монад

Денис Николаевич Москвин

СПбАУ РАН

19.04.2016

Денис Николаевич Москвин Трансформеры монад

Page 2: Функциональное программирование, весна 2016: Трансформеры монад

План лекции

1 Моноиды, Alternative, MonadPlus

2 Монады с обработкой ошибок

3 Мультипараметрические классы типов

4 Трансформеры монад

Денис Николаевич Москвин Трансформеры монад

Page 3: Функциональное программирование, весна 2016: Трансформеры монад

План лекции

1 Моноиды, Alternative, MonadPlus

2 Монады с обработкой ошибок

3 Мультипараметрические классы типов

4 Трансформеры монад

Денис Николаевич Москвин Трансформеры монад

Page 4: Функциональное программирование, весна 2016: Трансформеры монад

Вспомним класс типов Monoid

class Monoid a wheremempty :: amappend :: a -> a -> a

Некоторые аппликативные функторы и монады являются,помимо всего прочего, моноидами (списки, Maybe). Например,

instance Monoid a => Monoid (Maybe a) wheremempty = NothingNothing ‘mappend‘ m = mm ‘mappend‘ Nothing = mJust m1 ‘mappend‘ Just m2 = Just (m1 ‘mappend‘ m2)

Денис Николаевич Москвин Трансформеры монад

Page 5: Функциональное программирование, весна 2016: Трансформеры монад

Другой представитель Monoid для Maybe

Полезны и другие способы сделать Maybe моноидом, например

instance Monoid (Maybe a) wheremempty = NothingNothing ‘mappend‘ m = mm@(Just _) ‘mappend‘ _ = m

Поскольку для нельзя объявить двух представителей дляодного типа, в стандартной библиотеке используется упаковка

newtype First a = First { getFirst :: Maybe a }

В отличие от предыдущей реализации, параметризующийMaybe тип a совершенно не важен.

Денис Николаевич Москвин Трансформеры монад

Page 6: Функциональное программирование, весна 2016: Трансформеры монад

Класс Alternative

class Applicative f => Alternative f whereempty :: f a(<|>) :: f a -> f a -> f a

-- One or more.some :: f a -> f [a]some v = some_v where

many_v = some_v <|> pure []some_v = (:) <$> v <*> many_v

-- Zero or more.many :: f a -> f [a]many v = many_v where

many_v = some_v <|> pure []some_v = (:) <$> v <*> many_v

infixl 3 <|>

Денис Николаевич Москвин Трансформеры монад

Page 7: Функциональное программирование, весна 2016: Трансформеры монад

Представитель Alternative для Maybe

instance Alternative Maybe whereempty = Nothing

Nothing <|> m = mm@(Just _) <|> _ = m

Представитель Alternative для Maybe ведёт себя, как упаковкаFirst, возвращая первый не-Nothing в цепочке альтернатив:

Сессия GHCi

*Fp11> Nothing <|> (Just 3) <|> (Just 5) <|> NothingJust 3

Денис Николаевич Москвин Трансформеры монад

Page 8: Функциональное программирование, весна 2016: Трансформеры монад

Интерфейс Alternative для парсера Parsec

> parseTest (string "ABC") "ABC123""ABC"> let parser = (,) <$> string "ABC" <*> some digit> parseTest parser "ABC123"("ABC","123")> parseTest parser "ABC"parse error: unexpected end of input: expecting digit> let parser = (,) <$> string "ABC" <*> many digit> parseTest parser "ABC"("ABC","")> let parser = string "ABC" <|> string "DEF"> parseTest parser "ABC123""ABC"> parseTest parser "DEF123""DEF"> parseTest parser "GHI123"parse error: unexpected "G": expecting "ABC" or "DEF"

Денис Николаевич Москвин Трансформеры монад

Page 9: Функциональное программирование, весна 2016: Трансформеры монад

Законы Alternative

Помимо законов моноидальной структуры требуют выполнения

Right distributivity of <*>

(f <|> g) <*> a ≡ (f <*> a) <|> (g <*> a)

Right absorption for <*>

empty <*> a ≡ empty

Left distributivity of fmap

f <$> (a <|> b) ≡ (f <$> a) <|> (f <$> b)

Left absorption for fmap

f <$> empty ≡ empty

Денис Николаевич Москвин Трансформеры монад

Page 10: Функциональное программирование, весна 2016: Трансформеры монад

Класс MonadPlus

class (Alternative m, Monad m) => MonadPlus m wheremzero :: m amzero = emptymplus :: m a -> m a -> m amplus = (<|>)

Минимальное полное определение: ничего не делать.

instance MonadPlus Maybe

instance Alternative [] whereempty = [](<|>) = (++)

instance MonadPlus []

Денис Николаевич Москвин Трансформеры монад

Page 11: Функциональное программирование, весна 2016: Трансформеры монад

Законы MonadPlus

Помимо «унаследованных» законов требуют выполнения

Left and Right Zero

mzero >>= k ≡ mzerov >> mzero ≡ mzero

и по крайней мере одного из двух

Left Distribution

(a ‘mplus‘ b) >>= k ≡ (a >>= k) ‘mplus‘ (b >>= k)

Left Catch law

return a ‘mplus‘ b ≡ return a

Денис Николаевич Москвин Трансформеры монад

Page 12: Функциональное программирование, весна 2016: Трансформеры монад

Использование MonadPlus

-- Haskell 2010 :: MonadPlus m => Bool -> m ()guard :: Alternative f => Bool -> f ()guard True = pure ()guard False = empty

pythags = do z <- [1..]x <- [1..z]y <- [x..z]guard (x^2 + y^2 == z^2)return (x, y, z)

Сессия GHCi

*Fp11> take 5 pythags[(3,4,5),(6,8,10),(5,12,13),(9,12,15),(8,15,17)]

Денис Николаевич Москвин Трансформеры монад

Page 13: Функциональное программирование, весна 2016: Трансформеры монад

Использование MonadPlus (2)

msum :: (Foldable t, MonadPlus m) => t (m a) -> m amsum = foldr mplus mzero

mfilter :: MonadPlus m => (a -> Bool) -> m a -> m amfilter p ma = do

a <- maif p athen return aelse mzero

Денис Николаевич Москвин Трансформеры монад

Page 14: Функциональное программирование, весна 2016: Трансформеры монад

План лекции

1 Моноиды, Alternative, MonadPlus

2 Монады с обработкой ошибок

3 Мультипараметрические классы типов

4 Трансформеры монад

Денис Николаевич Москвин Трансформеры монад

Page 15: Функциональное программирование, весна 2016: Трансформеры монад

Монада Except

Вычисление, допускающее возбуждение и перехватисключений.

newtype Except e a = Except {runExcept :: Either e a}

except :: Either e a -> Except e aexcept = Except

instance Monad (Except e) wherereturn a = Except $ Right am >>= k = case runExcept m of

Left e -> Except $ Left eRight x -> k x

Денис Николаевич Москвин Трансформеры монад

Page 16: Функциональное программирование, весна 2016: Трансформеры монад

Стандартный интерфейс

throwE :: e -> Except e athrowE = except . Left

catchE :: Except e a -> (e -> Except e’ a) -> Except e’ am ‘catchE‘ h = case runExcept m of

Left l -> h lRight r -> Except $ Right r

Использование

do { action1; action2; action3 } ‘catchE‘ handler

В библиотеке mtl по историческим причинам throwError иcatchError.

Денис Николаевич Москвин Трансформеры монад

Page 17: Функциональное программирование, весна 2016: Трансформеры монад

Пример использования

data DivByError = ErrZero | Other String deriving (Eq,Show)

(/?) :: Double -> Double -> Except DivByError Doublex /? 0 = throwE ErrZerox /? y = return $ x / y

example0 :: Double -> Double -> Except DivByError Stringexample0 x y = action ‘catchE‘ handler where

action = do q <- x /? yreturn $ show q

handler = \err -> return $ show err

GHCi> runExcept $ example0 5 2Right "2.5"GHCi> runExcept $ example0 5 0Right "ErrZero"

Денис Николаевич Москвин Трансформеры монад

Page 18: Функциональное программирование, весна 2016: Трансформеры монад

Расширение функциональности

Если хотим использовать функциональность MonadPlus, то естьguard, msum, mfilter, нужно сделать Except e представителем:

instance Monoid e => MonadPlus (Except e) wheremzero = Except $ Left memptyExcept x ‘mplus‘ Except y = Except $

case x ofLeft e -> either (Left . mappend e) Right yr -> r

Семантика:mzero — ошибка по умолчанию для guard, задается mempty;mplus — накапливает ошибки слева направо, но еслипроисходит удачная попытка, то возвращает удачу.

Денис Николаевич Москвин Трансформеры монад

Page 19: Функциональное программирование, весна 2016: Трансформеры монад

Расширение функциональности, пример

instance Monoid DivByError wheremempty = Other ""Other s1 ‘mappend‘ Other s2 = Other $ s1 ++ s2Other s1 ‘mappend‘ ErrZero = Other $ s1 ++ "zero;"ErrZero ‘mappend‘ Other s2 = Other $ "zero;" ++ s2ErrZero ‘mappend‘ ErrZero = Other $ "zero;zero"

example2 :: Double -> Double -> Except DivByError Stringexample2 x y = action ‘catchE‘ handler where

action = doq <- x /? yguard $ y>=0return $ show q

handler = \err -> return $ show err

Денис Николаевич Москвин Трансформеры монад

Page 20: Функциональное программирование, весна 2016: Трансформеры монад

Расширение функциональности, пример (2)

example2 :: Double -> Double -> Except DivByError Stringexample2 x y = action ‘catchE‘ handler where

action = do q <- x /? yguard $ y>=0return $ show q

handler = \err -> return $ show err

GHCi> runExcept $ example2 5 0Right "ErrZero"GHCi> runExcept $ example2 5 (-2)Right "Other \"\""GHCi> runExcept $ msum [5/?0, 7/?0, 2/?0]Left (Other "zero;zero;zero;")GHCi> runExcept $ msum [5/?0, 7/?0, 2/?4]Right 0.5

Денис Николаевич Москвин Трансформеры монад

Page 21: Функциональное программирование, весна 2016: Трансформеры монад

План лекции

1 Моноиды, Alternative, MonadPlus

2 Монады с обработкой ошибок

3 Мультипараметрические классы типов

4 Трансформеры монад

Денис Николаевич Москвин Трансформеры монад

Page 22: Функциональное программирование, весна 2016: Трансформеры монад

Мотивирующий пример

Рассмотрим линейную алгебру в Z2

data Vector = Vector Int Intdata Matrix = Matrix Vector Vector

Хотим реализовать умножение так, чтобы можно было

(***) :: Matrix -> Matrix -> Matrix(***) :: Matrix -> Vector -> Vector(***) :: Matrix -> Int -> Matrix(***) :: Int -> Matrix -> Matrix...

Обычная сигнатура умножения из Num слишком бедна дляэтого.

Денис Николаевич Москвин Трансформеры монад

Page 23: Функциональное программирование, весна 2016: Трансформеры монад

Мультипараметрические классы типов

class Mult a b c where(***) :: a -> b -> c

instance Mult Matrix Matrix Matrix where{- ... -}

instance Mult Matrix Vector Vector where{- ... -}

instance Mult Matrix Int Matrix where{- ... -}

instance Mult Int Matrix Matrix where{- ... -}

...

Мультипараметрические классы типов являются расширениемстандарта и требуют прагмы{-# LANGUAGE MultiParamTypeClasses #-}.

Денис Николаевич Москвин Трансформеры монад

Page 24: Функциональное программирование, весна 2016: Трансформеры монад

Проблемы с использованием

К сожалению, такое решение слишком полиморфно:

Сессия GHCi

*Fp11> let a = Matrix (Vector 1 2) (Vector 3 4)*Fp11> let i = Matrix (Vector 1 0) (Vector 0 1)*Fp11> a *** i

No instance for (Mult Matrix Matrix c) arising froma use of ‘***’

The type variable ‘c’ is ambiguous*Fp11> (a *** i) :: MatrixMatrix (Vector 1 2) (Vector 3 4)

Типовая переменная c в действительности не являетсясвободной, но системе вывода типов нужна соответствующаяподсказка.

Денис Николаевич Москвин Трансформеры монад

Page 25: Функциональное программирование, весна 2016: Трансформеры монад

Функциональные зависимости

Можно задать «функциональную зависимость», указав, чтотип c уникальным образом определяется типами a и b

class Mult a b c | a b -> c where(***) :: a -> b -> c

Теперь все работает

Сессия GHCi

*Fp11> let a = Matrix (Vector 1 2) (Vector 3 4)*Fp11> let i = Matrix (Vector 1 0) (Vector 0 1)*Fp11> a *** iMatrix (Vector 1 2) (Vector 3 4)

Нужна прагма {-# LANGUAGE FunctionalDependencies #-}.

Денис Николаевич Москвин Трансформеры монад

Page 26: Функциональное программирование, весна 2016: Трансформеры монад

Использование в mtl

Стандартные интерфейсы стандартных монад упакованы в mtlв мультипараметрические классы типов, что позволяетнаделять любую монаду соответствующим интерфейсом.

class Monad m => MonadReader r m | m -> r whereask :: m rlocal :: (r -> r) -> m a -> m a

class (Monoid w, Monad m) => MonadWriter w m | m -> w wheretell :: w -> m ()listen :: m a -> m (a, w)

class Monad m => MonadState s m | m -> s whereget :: m sput :: s -> m ()

class (Monad m) => MonadError e m | m -> e wherethrowError :: e -> m acatchError :: m a -> (e -> m a) -> m a

Денис Николаевич Москвин Трансформеры монад

Page 27: Функциональное программирование, весна 2016: Трансформеры монад

Простейшие представители

«Неупакованные» контейнеры Either a b и (->) a b являютсяпредставителями MonadError a (Either a) иMonadReader a ((->) a), поэтому допустимо писать:

GHCi> import Control.Monad.ExceptGHCi> Left 5 ‘catchError‘ (\e -> Right (e^2))Right 25GHCi> Right 5 ‘catchError‘ (\e -> Right (e^2))Right 5GHCi> import Control.Monad.ReaderGHCi> do {x<-(*2); y<-ask; return (x+y)} $ 515

Денис Николаевич Москвин Трансформеры монад

Page 28: Функциональное программирование, весна 2016: Трансформеры монад

План лекции

1 Моноиды, Alternative, MonadPlus

2 Монады с обработкой ошибок

3 Мультипараметрические классы типов

4 Трансформеры монад

Денис Николаевич Москвин Трансформеры монад

Page 29: Функциональное программирование, весна 2016: Трансформеры монад

Трансформеры монад: знакомство

stInteger :: State Integer IntegerstInteger = do modify (+1)

a <- getreturn a

stString :: State String StringstString = do modify (++"1")

b <- getreturn b

*Fp11> evalState stInteger 01*Fp11> evalState stString "0""01"

Что делать если хотим в одном монадическом вычисленииработать с обоими состояниями?

Денис Николаевич Москвин Трансформеры монад

Page 30: Функциональное программирование, весна 2016: Трансформеры монад

Monad transformers are like onions

stComb :: StateT Integer(StateT String Identity)(Integer, String)

stComb = do modify (+1)lift $ modify (++"1")a <- getb <- lift $ getreturn (a,b)

*Fp11> runIdentity $ evalStateT (evalStateT stComb 0) "0"(1,"01")

В качестве основы помимо Identity используют также IO соспециализированной liftIO.

Денис Николаевич Москвин Трансформеры монад

Page 31: Функциональное программирование, весна 2016: Трансформеры монад

Трансформеры монад

Трансформер монад — конструктор типа, которыйпринимает монаду в качестве параметра и возвращает монадукак результат.

Требования:1 Поскольку у монады кайнд m : * -> *, у трансформера

должен быть кайнд t: (* -> *) -> * -> *2 Для любой монады m, аппликация t m должна быть

монадой, то есть её return и (>>=) должны удовлетворятьзаконам монад.

3 Нужен lift :: m a -> t m a, «поднимающий» значениеиз трансформируемой монады в трансформированную.

В библиотеке transformers функция lift всегда вызываетсявручную, в mtl — только для неоднозначных ситуаций.

Денис Николаевич Москвин Трансформеры монад

Page 32: Функциональное программирование, весна 2016: Трансформеры монад

Рецепт приготовления трансформера для MyMonad (1)

1. У трансформера должен быть кайндt: (* -> *) -> * -> *Определяем наш конкретный трансформер MyMonadT длямонады MyMonad

newtype MyMonadT m a= MyMonadT { runMyMonadT :: m (MyMonad a) }

Такое определение согласовано с механизмом вызова

comp :: (MyMonadT Identity) a

runIdentity (runMyMonadT comp) :: a

(«Сцепляющая» вычисления конструкция может быть болеесложной чем m (MyMonad a) и зависит от конкретной семантикиэффектов MyMonad.)

Денис Николаевич Москвин Трансформеры монад

Page 33: Функциональное программирование, весна 2016: Трансформеры монад

Рецепт приготовления трансформера для MyMonad (2)

2. Для любой монады m, аппликация t m должна быть монадойДелаем аппликацию нашего трансформера к монаде(MyMonadT m) представителем Monad

instance (Monad m) => Monad (MyMonadT m) wherereturn x = ...mx >>= k = ...

Денис Николаевич Москвин Трансформеры монад

Page 34: Функциональное программирование, весна 2016: Трансформеры монад

Рецепт приготовления трансформера для MyMonad (3)

3. Операция lift :: m a -> t m a из class MonadTransПоднимаем значение из трансформируемой монады втрансформированную

class MonadTrans t wherelift :: (Monad m) => m a -> t m a

instance MonadTrans MyMonadT wherelift mx = ...

Денис Николаевич Москвин Трансформеры монад

Page 35: Функциональное программирование, весна 2016: Трансформеры монад

Законы для класса типов MonadTrans

Функция lift для любого представителя MonadTrans должнаудовлетворять следующим законам

Right Zero – Правый ноль

lift . return ≡ return

Left Distribution – Левая дистрибутивность

lift (m >>= k) ≡ lift m >>= (lift . k)

Денис Николаевич Москвин Трансформеры монад

Page 36: Функциональное программирование, весна 2016: Трансформеры монад

Трансформер для Maybe — шаги (1) и (3)

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

MaybeT :: m (Maybe a) -> MaybeT m arunMaybeT :: MaybeT m a -> m (Maybe a)

instance MonadTrans MaybeT wherelift :: m a -> MaybeT m alift = MaybeT . liftM Just

Пояснение работы lift при поднятии get из State:

*Fp11> :t getget :: MonadState s m => m s*Fp11> :t lift getlift get :: (MonadState s m, MonadTrans t) => t m s

Здесь m (mtl) можно читать как State s (transformers).Денис Николаевич Москвин Трансформеры монад

Page 37: Функциональное программирование, весна 2016: Трансформеры монад

Трансформер для Maybe — шаг (2)

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

instance (Monad m) => Monad (MaybeT m) wherefail :: String -> MaybeT m afail _ = MaybeT $ return Nothing

return :: a -> MaybeT m areturn = lift . return

(>>=) :: MaybeT m a -> (a -> MaybeT m b) -> MaybeT m bmx >>= k = MaybeT $ do -- inner monad do

v <- runMaybeT mxcase v of

Nothing -> return NothingJust y -> runMaybeT (k y)

Денис Николаевич Москвин Трансформеры монад

Page 38: Функциональное программирование, весна 2016: Трансформеры монад

Пример использования MaybeT

mbSt :: MaybeT (StateT Integer Identity) IntegermbSt = do

lift $ modify (+1)a <- lift getTrue <- return $ a >= 3return a

*Fp11> runIdentity $ evalStateT (runMaybeT mbSt) 0Nothing*Fp11> runIdentity $ evalStateT (runMaybeT mbSt) 2Just 3

Если хотим guard $ a >= 3 нужно сделать MaybeT mпредставителем MonadPlus

Денис Николаевич Москвин Трансформеры монад

Page 39: Функциональное программирование, весна 2016: Трансформеры монад

Пример использования MaybeT (2)

instance (Monad m) => MonadPlus (MaybeT m) wheremzero = MaybeT $ return Nothingx ‘mplus‘ y = MaybeT $ do

v <- runMaybeT xcase v of Nothing -> runMaybeT y

Just _ -> return v--instance (Monad m) => Alternative (MaybeT m) where...--GHC>=7.10

mbSt’ :: MaybeT (State Integer) IntegermbSt’ = do lift $ modify (+1)

a <- lift getguard $ a >= 3 -- !!return a

*Fp11> runIdentity $ evalStateT (runMaybeT mbSt’) 2Just 3

Денис Николаевич Москвин Трансформеры монад

Page 40: Функциональное программирование, весна 2016: Трансформеры монад

Пример использования MaybeT (3)

Для любой пары монад можно избавиться от подъёмастандартных операций вложенной монады.Например, для монады State стандартный интерфейс упакован(в библиотеке mtl) в класс типов с фундепсами

class Monad m => MonadState s m | m -> s whereget :: m sput :: s -> m ()state :: (s -> (a, s)) -> m a

Мы можем реализовать для MaybeT

instance (MonadState s m) => MonadState s (MaybeT m) whereget = lift getput = lift . put

Денис Николаевич Москвин Трансформеры монад

Page 41: Функциональное программирование, весна 2016: Трансформеры монад

Пример использования MaybeT (3)

Теперь можно не поднимать явно стандартные операции State

mbSt’’ :: MaybeT (State Integer) IntegermbSt’’ = do

modify (+1) -- без lifta <- get -- без liftguard $ a >= 3return a

*Fp11> runIdentity $ evalStateT (runMaybeT mbSt’’) 2Just 3

Денис Николаевич Москвин Трансформеры монад

Page 42: Функциональное программирование, весна 2016: Трансформеры монад

Таблица стандартных трансформеров

Монада Трансформер Исходный тип Тип трансформераExcept ExceptT Either e a m (Either e a)State StateT s -> (a,s) s -> m (a,s)Reader ReaderT r -> a r -> m aWriter WriterT (a,w) m (a,w)Cont ContT (a -> r) -> r (a -> m r) -> m r

Они определены в библиотеке mtl. Более того, первый столбецопределён через второй:

type Writer w = WriterT w Identitytype Reader r = ReaderT r Identitytype State s = StateT s Identity

...

Денис Николаевич Москвин Трансформеры монад

Page 43: Функциональное программирование, весна 2016: Трансформеры монад

Что во что вкладывать?

Если нам нужна функциональность Except и State, то естьнаша монада должна быть представителем MonadError иMonadState.Должны ли мы применять трансформер StateT к монадеExcept или трансформер ErrorT к монаде State?Решение зависит от того, какой в точности семантики мыожидаем от комбинированной монады.

Денис Николаевич Москвин Трансформеры монад

Page 44: Функциональное программирование, весна 2016: Трансформеры монад

Что во что вкладывать?

Применение StateT к монаде Except даёт функциютрансформирования типа s -> Either e (a, s).Применение ExceptT к монаде State даёт функциютрансформирования типа s -> (Either e a, s).Порядок зависит от той роли, которую ошибка играет ввычислениях.Если ошибка обозначает, что состояние не может бытьвычислено, то нам следует применять StateT к Except.Если ошибка обозначает, что значениене не может бытьвычислено, но состояние при этом не «портится», то намследует применять ExceptT к State.

Денис Николаевич Москвин Трансформеры монад