Haskell 状态Monad (State Monad)的理解

1、State s a数据类型

State s a是一种用来封装状态处理函数:\s -> (a,s’)的数据结构,因为State封装的是一个函数,而不是状态s本身,所以称State为State类型(type)是不准确的,应该把State s a称为状态处理器(State processor)。

newtype State s a = State { runState :: s -> (a,s) }
-- 即:newtype State s a = State (s -> (a,s))
  • 注意】(\s -> (a,s’)) 是状态处理函数,封装了状态处理函数的State容器叫做状态处理器,不要混淆了。

  • 尽管将State称为类型不准确,但毕竟State s a是通过 newtype定义的,也就是对现有类型的封装。我们可以不准确地将其称为State数据类型,该类型有两个类型参数:表示状态的类型参数 s 以及表示结果的类型参数 a。虽然写作State s a,但在理解上,我们应该把State看成是一个装了状态处理函数的容器State (s -> (a, s)),即状态处理器。

  • State s a的实质是State (s -> (a, s)),State s a与State (s -> (a, s))完全等价。

  • 通过 runState 访问函数可以从 State 类型中取出这个封装在里面的状态处理函数:\s -> (a,s’),该函数接收一个状态参数 s,经过计算(转换)之后返回一个由状态s下对应的返回结果 a 以及新的状态 s’的二元组(a, s’)。runState可以直接当作状态处理函数的函数名,他相当于把State容器中的状态处理函数从State容器中取出来。


2、State s的Monad实例

instance Monad (State s) where
return :: x -> State s x 
return x = state (\s -> (x, s))    --注意这里state的s是小写
-- return x = State $ \s -> (x,s)  --注意这里的State的S是大写

(>>=) :: State s a -> (a -> State s b) -> State s b
pr >>= f = state $ \ st ->
   let (x, st') = runState pr st   -- Running the first processor on st
   in runState (f x) st'           -- Running the second processor on st'
  • state :: (s -> (a, s)) -> State s a 即:把一个状态处理函数封装到State容器里

  • return函数把结果x封装成一个State s x即State (s -> (x, s))的状态处理器

  • bind 函数的类型签名为:(>>=) :: State s a -> (a -> State s b) -> State s b
    大致相当于 State (s -> (a, s)) -> (a -> State (s -> (b, s))) -> State (s -> (b, s))

  • 函数(a -> State s b)相当于一个生成状态处理器State (s -> (b, s))的函数,称为状态处理器生成函数f。

  • 整个bind函数最后返回的结果为一个状态处理器State(s -> (b, s)),即用装入了状态处理函数的State容器

在这里插入图片描述

(1) 对bind函数(>>=)的理解:

  1. bind运算的左侧为一个状态处理器pA (写为State s a类型,理解为装了一个状态转换函数的State容器State (\s1 -> (v1, s2)))

  2. bind运算的右侧为一个状态处理器生成函数f (即: \v1 -> State (\s2 -> (v2, s3))),该函数接受一个结果v1,返回一个状态处理器pB(即:State (\s2 -> (v2, s3)))

  3. 我们将pA >>= f 产生pB的这一个过程记为pAB,即pAB = pA >>= f,pAB为一个封装了由s1到(v2, s3)的State容器

【注意】pB与pAB不是一样的,pB是封装了(\s2 -> (v2, s3)) 的State容器,而pAB是封装了(\s1 -> (v2, s3)) 的State容器

(2) 数据流向:

  1. 首先给定一个初始状态s1(常称为seed)和一个状态处理器pA,在这个状态s1下,我们通过runState把状态处理器pA中的状态处理函数取出来,并向该函数传入状态s1,得到一个包含状态s1下对应的返回结果v1以及新状态s2的二元组(v1,s2)

  2. 通过状态处理器生成函数f,我们将得到的二元组(v1,s2)中的v1取出传入f中,得到一个新的状态处理器pB。之所以要把v1取出传给函数f是因为v1是State s这个Monad中State s a的a,而f需要接收一个State s a中的a,从而生成一个State s b(即pB)

  3. 通过runState函数将pB中的状态处理函数取出来,传入新的状态s2,得到一个包含状态s2下对应的返回结果v2以及新状态s3的二元组(v2,s3)

(3) 做bind的意义

在计算 pA >> f 的过程中,我们产生了两个不同状态(s1、 s2)下对应的两个结果(v1、v2),并将状态更新为了s3

这就像我们在做伪随机投骰子一样,得到了两次投掷结果v1、v2,并将状态跟新了,以便下次能继续根据新状态s3产生投掷结果

我们可以把状态处理器pA、pB理解成骰子,状态处理器生成函数 f 理解为骰子生成器。相同状态下骰子产生的结果是唯一的。

  • 首先我们有一个状态s1和一个骰子pA,骰子pA根据状态s1投出了结果v1,并且将状态更新为s2
  • 然后骰子生成器 f 接收到v1,得知骰子pA已经被使用,随后生成一个新的骰子pB,新的骰子pB根据新的状态s2投出了新的结果v2,并将状态更新为s3。

所以:
p >>= f 可以理解为投了两次骰子得到了两个结果
p >>= f >>= f 可以理解为投了三次骰子得到三个结果
以此类推…


3、State Monad中的相关函数

(1) 设置与读取State

在前面,我们讨论了给定一个初始状态s和一个状态处理器p,我们可以得到一个返回结果a和一个新的状态s’。初始状态的话我们可以直接设定,那么初始状态处理器是怎么得到的呢呢?

通过put函数生成状态处理器:

put newState = State $ \_ -> ((), newState)
  • put函数首先接收一个我们给定的初始状态newState,然后生成一个状态处理器State (_ -> ((), newState))。这个状态处理器忽略它接收到的任何state参数,然后返回一个二元组,这个二元组里包含一个空结果()和输入给put函数的初始状态newState。

  • 由于我们不需要这个初始状态newState下返回的结果,我们只是需要一个状态来替换,所以元组的第一个元素是(),一个占位的值(universal placeholder value)。

(2) 获取值与状态

通过evalState函数,我们可以获取状态处理器p在传入状态s下,返回的结果a

evalState :: State s a -> s -> a
evalState p s = fst (runState p s)

通过execState函数,我们可以获取状态处理器p在传入状态s后,生成的新状态s’

execState :: State s a -> s -> s
execState p s = snd (runState p s)

猜你喜欢

转载自blog.csdn.net/flavioy/article/details/103431804
今日推荐