Haskell 有助于理解这个 State monad 代码:runState 在哪里定义的?

Haskell help to understand this State monad code: where is runState defined?

我是 Haskell 的新手,正在尝试了解 monad。我要通过 this code 放在这里以供快速参考

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

instance Monad (State s) where
  return a = State $ \s -> (a, s)

  State act >>= k = State $ \s ->
    let (a, s') = act s
    in runState (k a) s'

get :: State s s
get = State $ \s -> (s, s)

put :: s -> State s ()
put s = State $ \_ -> ((), s)

modify :: (s -> s) -> State s ()
modify f = get >>= \x -> put (f x)

evalState :: State s a -> s -> a
evalState act = fst . runState act

execState :: State s a -> s -> s
execState act = snd . runState act

我没有找到函数 runState 的定义位置。它的类型似乎在 newtype State s a = State { runState :: s -> (a,s) } 中声明。 最令人费解的部分是这段代码编译没有任何错误。

runState在哪里定义的?它的代码在哪里?

是的,你是对的,它在这里定义:

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

基本上,根据这样的定义,您可以免费获得两个 函数

  • State :: (s -> (a,s)) -> State s a 构造函数(命名与类型相同)- 在这里你可以 wrap 在函数中进入 newtype
  • 来自记录语法内部的
  • runState :: State s a -> (s -> (a,s))(与 State s a -> s -> (a,s) 相同) - 这将从第一个参数中取出 wrapped 函数并应用它到第二个参数(或 return 函数返回 - currying 使我们能够很好地处理这两种解释)

从某种意义上说,在创建一个新的State s a值时编写它的代码——它会取这个值,得到内容 并应用它。

所以,例如,如果你这样做

runState get "Hello"
{ def. get }
= runState (State (\s -> (s,s))) "Hello"
{ apply runState }
= (\s -> (s,s)) "Hello"
{ apply }
= ("Hello", "Hello")

关于你的问题——据我所知——记录语法等于:

newtype State s a = State (s -> (a,s))

runState :: State s a -> s -> (a,s)
runState (State f) s = f s

您似乎缺少的是记录语法 提供的功能。在Haskell中,定义数据类型有多种方式。您可能熟悉如下语句:

data MyType = MyType String Int

其中定义了类型 MyType,它是求和类型(即此类型的任何值都具有 StringInt 字段已填充)。如果我们想使用这种类型,我们可能想分别查看 StringInt 组件,因此我们会为此定义访问器函数:

getLabel :: MyType -> String
getLabel (MyType s _) = s

getNum :: MyType -> Int
getNum (MyType _ i) = i

对于大型数据类型(有很多字段),这会变得非常乏味,编写这种函数通常很常见:我们通常有一个简单的类型,其中构造函数包装内容,我们想要一个方便的以 "own" 类型(即没有包装器)访问此内容的方法。

由于这种访问字段的需求非常普遍,Haskell 提供了记录语法。当您使用记录语法时,您实质上是在数据类型中命名字段,并且自动生成用于提取这些字段中的值的函数。所以我们可以重新定义我们之前的数据类型:

data MyType' = MyType' { myLabel :: String, myNum :: Int }

和 Haskell 会自动生成访问器函数 myLabel :: MyType' -> StringmyNum :: MyType' -> Int。这些函数与我们之前定义的 getLabelgetNum 函数具有相同的功能。

State类型为例,我们可以这样定义:

newtype State' s a = State' (s -> (a, s))

并编写了一个函数来访问内容:

getStateFun :: State' s a -> (s -> (a, s))
getStateFun (State' f) = f

这将允许我们从我们的值周围删除 State' "wrapping"。但这不如使用示例中使用的记录语法方便:存储在 State 包装器内的值被赋予字段名 runState,并且 Haskell 生成访问器函数,它这就是代码编译和运行没有问题的原因。 runState 然后基本上与 getStateFun 相同。

这在 this chapter from Learn You A Haskell For Great Good 的 "Record syntax" 部分也有很清楚的解释。