查看Learn You a Haskell对State Monad
的定义:
instance Monad (State s) where
return x = State $ \s -> (x,s)
(State h) >>= f = State $ \s -> let (a, newState) = h s
(State g) = f a
in g newState
我不明白右下角的h s
和g newState
类型。
你能解释一下他们的类型和发生的事情吗?
答案 0 :(得分:7)
State s a
是函数的命名---“状态变换函数”
s -> (a, s)
换句话说,它需要输入状态s
并修改该状态,同时返回结果a
。这形成了一个真正的“纯国家”框架。如果我们的状态是一个整数,我们可以编写一个更新该整数并返回新值的函数---这就像一个唯一的数字源。
upd :: Int -> (Int, Int)
upd s = let s' = s + 1 in (s', s')
此处,a
和s
最终属于同一类型。
现在这一切都很好,除非我们想要获得两个新数字,我们遇到了麻烦。为此,我们必须以某种方式运行upd
两次。
最终结果将是另一个状态变压器功能,所以我们正在寻找一个“状态变压器变压器”。我会称它为撰写:
compose :: (s -> (a, s)) -- the initial state transformer
-> (a -> (s -> (b, s))) -- a new state transformer, built using the "result"
-- of the previous one
-> (s -> (b, s)) -- the result state transformer
这看起来有点毛茸茸,但说实话,写这个功能相当容易。这些类型可以指导您找到答案:
compose f f' = \s -> let (a, s') = f s
(b, s'') = f' a s'
in (b, s'')
您会注意到s
- 类型变量[s, s', s'']
“向下流动”,表示状态从第一次计算移动到第二次计算结果。
我们可以使用compose
构建一个使用upd
获取两个唯一数字的函数
twoUnique :: Int -> ((Int, Int), Int)
twoUnique = compose upd (\a s -> let (a', s') = upd s in ((a, a'), s'))
这些是State
的基础知识。唯一的区别是我们认识到compose
函数内部存在一个共同模式,我们将其提取出来。该模式看起来像
(>>=) :: State s a -> (a -> State s b ) -> State s b
(>>=) :: (s -> (a, s)) -> (a -> (s -> (b, s)) -> (s -> (b, s))
它也以同样的方式实现。我们只需要“包裹”和“展开”State
位 - 这就是State
和runState
State :: (s -> (a, s)) -> State s a
runState :: State s a -> (s -> (a, s))
现在我们可以compose
并将其与(>>=)
compose f f' = \s -> let (a, s') = f s
(b, s'') = f' a s'
in (b, s'')
(>>=) (State f) f' = State $ \s -> let (a, s') = f s
(b, s'') = runState (f' a) s'
in (b, s'')
答案 1 :(得分:2)
State
Monad肯定会在您第一次看到它时感到困惑。首先要理解的是它的数据声明,即
newtype State s a = State { runState :: s -> (a,s) }
因此State
包含类型为s -> (a,s)
的函数。我们可以将其视为一种作用于某种生成器并返回值的元组和新生成器的函数。这就是随机数在Haskell中的工作方式,例如:s
是生成器,而a
是函数的结果,它将生成器作为输入并输出随机数a
(比如说,Int
类型,但它可以很容易地成为任何其他类型。)
现在让我们来谈谈实例声明。回想一下(>>=)
的类型是
Monad m => m a -> (a -> m b) -> m b
我们特别注意f
应该有a -> m b
类型。在这种情况下,m
为State s
,因此f
的类型应为a -> State s b
。所以现在我们可以分解实例声明
(State h) >>= f = State $ \s -> let (a, newState) = h s
(State g) = f a
in g newState
由于f
的类型为a -> State s b
,因此State g
的类型必须为State s b
(即g :: s -> (b,s)
),因为h
具有类型s -> (a,s)
,我们必须newState :: s
。因此,绑定表达式的结果是g newState
,其类型为(b, s)
。
为了进一步阅读,here是一篇很棒的文章,帮助我在第一次遇到它时理解了State
Monad。
答案 2 :(得分:1)
根据LYAH State
monad的定义:
newtype State s a = State { runState :: s -> (a,s) }
这意味着State
数据构造函数的参数是函数,它接受一个状态并产生a
和一个新状态。因此,上例中的h
是一个函数,h s
计算a
和newState
。
从Hoogle我们看到(>>=)
的定义是
(>>=) :: Monad m => m a -> (a -> m b) -> m b
这意味着f
也是a
到State s b
的函数。因此,赋予f
参数a
是有意义的,结果是State
。就像h
一样,g
是状态构造函数的参数,它接受一个状态(在本例中为newstate
)并返回一对(a,newState2)
。
询问(>>=)
实际做什么可能更有启发性:它将函数参数提升为monad。 State
只是一个值的占位符,取决于当前状态,这就是构造函数的参数取决于状态的原因。因此,给定State
“值”,我们首先应用状态\s -> let (a, newState) = h s
来获取相应的值和新状态。现在我们将该值传递给函数(注意类型匹配)并获得一个新状态,即从状态到值的新函数。最后,我们在newState
评估该状态,以将状态线程化到计算的下一部分。