Как вывести государственную монаду из первых принципов? - PullRequest
0 голосов
/ 18 сентября 2018

Я пытаюсь придумать реализацию State Monad, основанную на примерах композиции функций.Вот что я придумал:

Сначала выведем понятие Монады:

data Maybe' a = Nothing' | Just' a deriving Show

sqrt' :: (Floating a, Ord a) => a -> Maybe' a
sqrt' x = if x < 0 then Nothing' else Just' (sqrt x)

inv' :: (Floating a, Ord a) => a -> Maybe' a
inv' x = if x == 0 then Nothing' else Just' (1/x)

log' :: (Floating a, Ord a) => a -> Maybe' a
log' x = if x == 0 then Nothing' else Just' (log x)

Мы можем иметь функцию, которая составляет следующие функции:

sqrtInvLog' :: (Floating a, Ord a) => a -> Maybe' a
sqrtInvLog' x = case (sqrt' x) of
                  Nothing' -> Nothing'
                  (Just' y) -> case (inv' y) of
                                Nothing' -> Nothing'
                                (Just' z) -> log' z

Это можно упростить, выделив оператор case и приложение-функцию:

fMaybe' :: (Maybe' a) -> (a -> Maybe' b) -> Maybe' b
fMaybe' Nothing' _ = Nothing'
fMaybe' (Just' x) f = f x

-- Applying fMaybe' =>
sqrtInvLog'' :: (Floating a, Ord a) => a -> Maybe' a
sqrtInvLog'' x = (sqrt' x) `fMaybe'` (inv') `fMaybe'` (log')`

Теперь мы можем обобщить концепцию на любой тип, а не просто Maybe, определив Monad =>

class Monad' m where
  bind' :: m a -> (a -> m b) -> m b
  return' :: a -> m a

instance Monad' Maybe' where
  bind' Nothing' _ = Nothing'
  bind' (Just' x) f = f x
  return' x = Just' x

Используя реализацию Monad, sqrtInvLog 'можно записать в виде:

sqrtInvLog''' :: (Floating a, Ord a) => a -> Maybe' a
sqrtInvLog''' x = (sqrt' x) \bind'` (inv') `bind'` (log')`

Пытаясь применить концепцию для поддержания состояния, я определил что-то, как показано ниже:

data St a s = St (a,s) deriving Show

sqrtLogInvSt' :: (Floating a, Ord a) => St a a -> St (Maybe' a) a
sqrtLogInvSt' (St (x,s)) = case (sqrt' x) of
                             Nothing' -> St (Nothing', s)
                             (Just' y) -> case (log' y) of
                                            Nothing' -> St (Nothing', s+y)
                                            (Just' z) -> St (inv' z, s+y+z)

Невозможно определить монаду, используя приведенное выше определение, так как привязка должна быть определена как получение в единственном типе "ma".

Вторая попытка, основанная на определении Хаскеллом State Monad:

newtype State s a = State { runState :: s -> (a, s) }

Первая попытка определить функцию, которая построена с использованием составных функций и поддерживает состояние:

fex1 :: Int->State Int Int
fex1 x = State { runState = \s->(r,(s+r)) } where r = x `mod` 2`

fex2 :: Int->State Int Int
fex2 x = State { runState = \s-> (r,s+r)} where r = x * 5

Составная функция:

fex3 x = (runState (fex2 y)) st where (st, y) = (runState (fex1 x)) 0

Но определение newtype State s a = State { runState :: s -> (a, s) } не соответствуетобразецm a -> (a -> m b) -> m b of bind

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

instance Monad' (State s) where
   bind' st f = undefined
   return' x = State { runState = \s -> (x,s) }

bind 'не определено выше, потому что я не знал, как я это реализую.

Я мог бы понять, почему монады полезны, и применить его в первом примере (возможно), но, похоже, не могу применить его к государству.Будет полезно понять, как я мог получить Государственный Моанд, используя понятия, определенные выше.

Обратите внимание, что я задавал подобный вопрос ранее: Haskell - Невозможно определить функцию, подобную Монаде, используя Монаду.как определение , но я расширил здесь и добавил больше деталей.

Ответы [ 2 ]

0 голосов
/ 19 сентября 2018

Посмотрите на это . Суммируя и расширяя немного.

Если у вас есть функция

ta -> tb

и хотите добавить к нему «состояние», тогда вы должны передать это состояние и иметь

(ta, ts) -> (tb, ts)

Вы можете преобразовать это, карри это:

ta -> ts -> (tb, ts)

Если сравнить это с исходным ta -> tb, мы получим (добавив скобки)

ta -> (ts -> (tb, ts))

Подводя итог, если функция возвращает tb из ta (т. Е. ta -> tb), «с состоянием» его версия вернется (ts -> (tb, ts)) из ta (т.е. ta -> (ts -> (tb, ts)))

Следовательно, «вычисление с состоянием» (только одна функция или цепочка функций, связанных с состоянием) должно возвращать / производить это:

(ts -> (tb, ts))

Это типичный случай стека целых. [Int] является государством

pop :: [Int] -> (Int, [Int]) -- remove top
pop (v:s) -> (v, s)

push :: Int -> [Int] -> (int, [Int]) -- add to the top
push v s -> (v, v:s)

Для push тип push 5 совпадает с типом pop :: [Int] -> (Int, [Int]). Поэтому мы хотели бы объединить / объединить эти основные операции, чтобы получить все как

duplicateTop =
   v <- pop
   push v
   push v

Обратите внимание, что при желании duplicateTop :: [Int] -> (Int, [Int])

Теперь: как объединить два вычисления с сохранением состояния, чтобы получить новое? Давайте сделаем это (Внимание: это определение не совпадает с используется для bind монады (>>=), но это эквивалентно).

Объединить:

f :: ta -> (ts -> (tb, ts))

с

g :: tb -> (ts -> (tc, ts))

чтобы получить

h :: ta -> (ts -> (tc, ts))

Это конструкция h (в псевдо-хаскеле)

h = \a -> ( \s -> (c, s') )

, где мы должны вычислить (c, s') (остальное в выражениях - это просто параметры a и s). Вот как:

                   -- 1. run f using a and s
  l1 = f a         -- use the parameter a to get the function (ts -> (tb, ts)) returned by f 
  (b, s1) = l1 s   -- use the parameter s to get the pair that l1 returns ( :: (tb, ts) )
                   -- 2. run g using f output, b and s1
  l2  = g b        -- use b to get the function (ts -> (tb, ts)) returned by g
  (c, s') = l2 s1  -- use s1 to get the pair that l2 returns ( :: (tc, ts) ) 
0 голосов
/ 18 сентября 2018

Ваша составная функция fex3 имеет неправильный тип:

fex3 :: Int -> (Int, Int)

В отличие от вашего sqrtInvLog' примера для Maybe ', State не отображается в типе fex3.

Мы можем определить его как

fex3 :: Int -> State Int Int
fex3 x = State { runState = \s ->
    let (y, st) = runState (fex1 x) s in
        runState (fex2 y) st }

Основное отличие вашего определения в том, что вместо жесткого кодирования 0 в качестве исходного состояния мы передаем собственное состояние s.

Что если (как в вашем Maybe примере) мы захотим составить три функции?Здесь я просто буду использовать fex2 вместо введения другой промежуточной функции:

fex4 :: Int -> State Int Int
fex4 x = State { runState = \s ->
        let (y, st) = runState (fex1 x) s in
            let (z, st') = runState (fex2 y) st in
                runState (fex2 z) st' }

SPOILERS:

Обобщенныйверсию bindState можно извлечь следующим образом:

bindState m f = State { runState = \s ->
    let (x, st) = runState m s in
    runState (f x) st }

fex3' x = fex1 x `bindState` fex2
fex4' x = fex1 x `bindState` fex2 `bindState` fex2

Мы также можем начать с Monad' и типов.

m в определении Monad'применяется к одному типу аргумента (m a, m b).Мы не можем установить m = State, потому что State требует двух аргументов.С другой стороны, частичное применение вполне подходит для типов: State s a действительно означает (State s) a, поэтому мы можем установить m = State s:

instance Monad' (State s) where
   -- return' :: a -> m a (where m = State s)
   -- return' :: a -> State s a
   return' x = State { runState = \s -> (x,s) }

   -- bind' :: m a -> (a -> m b) -> m b (where m = State s)
   -- bind' :: State s a -> (a -> State s b) -> State s b
   bind' st f =
   -- Good so far: we have two arguments
   --   st :: State s a
   --   f :: a -> State s b
   -- We also need a result
   --   ... :: State s b
   -- It must be a State, so we can start with:
       State { runState = \s ->
   -- Now we also have
   --   s :: s
   -- That means we can run st:
           let (x, s') = runState st s in
   --   runState :: State s a -> s -> (a, s)
   --   st :: State s a
   --   s :: s
   --   x :: a
   --   s' :: s
   -- Now we have a value of type 'a' that we can pass to f:
   --   f x :: State s b
   -- We are already in a State { ... } context, so we need
   -- to return a (value, state) tuple. We can get that from
   -- 'State s b' by using runState again:
           runState (f x) s'
       }
...