如何从第一性原理推导一个状态单子?

时间:2018-09-18 05:46:54

标签: haskell monads state-monad

我试图提出一个从功能组合示例派生的State Monad的实现。在这里,我想出了什么:

首先衍生出Monad的概念:

<table>
   <thead>
      <tr class="title">
         <th colspan="7">Size Table</th>
      </tr>
      <tr class="sub-title">
         <th rowspan="2"></th>
         <th colspan="2">Bust</th>
         <th colspan="2">Bust</th>
         <th colspan="2">Bust</th>
      </tr>
      <tr class="sub-title">
         <th>CH</th>
         <th>CM</th>
         <th>CH</th>
         <th>CM</th>
         <th>CH</th>
         <th>CM</th>
      </tr>
   </thead>
   <tbody>
      <tr>
         <td>S</td>
         <td>col1</td>
         <td>col2</td>
         <td>col3</td>
         <td>Col4</td>
         <td>Col5</td>
         <td>Col6</td>
      </tr>
      <tr>
         <td>M</td>
         <td>col1</td>
         <td>col2</td>
         <td>col3</td>
         <td>Col4</td>
         <td>Col5</td>
         <td>Col6</td>
      </tr>
      <tr>
         <td>L</td>
         <td>col1</td>
         <td>col2</td>
         <td>col3</td>
         <td>Col4</td>
         <td>Col5</td>
         <td>Col6</td>
      </tr>
   </tbody>
   <tfoot>
      <tr class="footer">
         <th colspan="7">description</th>
      </tr>
   </tfoot>

</table>

我们可以具有如下组成这些功能的功能:

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)

可以通过排除case语句和函数应用程序来简化此操作:

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

现在,通过定义Monad =>

,我们可以将概念推广到任何类型,而不仅仅是Maybe'。
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')`

使用Monad的实现,sqrtInvLog可以写为:

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

尝试应用概念维护状态,我定义了如下所示的内容:

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

无法使用上述定义来定义monad,因为bind需要定义为采用单一类型“ ma”。

基于Haskell对State Monad的定义的第二次尝试:

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)

首次尝试定义使用组合函数构建并保持状态的函数:

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
上面未定义

bind',因为我不知道如何实现。

我可以得出单子为什么有用的理由,并将其应用于第一个示例(也许是),但似乎无法将其应用于州。理解我如何使用上面定义的概念来推导州联结将很有用。

请注意,我之前曾问过类似的问题:Haskell - Unable to define a State monad like function using a Monad like definition,但我在这里进行了扩展并添加了更多详细信息。

2 个答案:

答案 0 :(得分:3)

您的组合函数fex3的类型错误:

fex3 :: Int -> (Int, Int)

与您的sqrtInvLog'的{​​{1}}示例不同,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

SPOILERS:

广义版本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' } 可以按以下方式提取:

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

答案 1 :(得分:0)

看看this。总结并扩展一点。

如果您有功能

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的典型情况。 [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)

对于pushpush 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(在伪haskell中)的构造

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

我们必须计算(c, s')(表达式中的其余部分只是参数as)。这是这样的:

                   -- 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) )