首先,State有两个类型参数:
以及
计算的最终结果
. 我们将使用
stateData
result
分别作为它们的类型变量。如果你仔细想想,这是有道理的;基于状态的计算的定义特征是,它在生成输出时修改状态。
不太明显的是,类型构造函数采用
功能
从状态到修改状态和结果,如下所示:
newtype State stateData result = State (stateData -> (result, stateData))
因此,虽然monad被称为“State”,但由monad包装的实际值是
基于状态的计算
记住这一点,我们发现函数
runState
用于执行状态monad中的计算实际上只不过是包装函数本身的访问器,可以这样定义:
runState (State f) = f
len2State :: String -> State Int Bool
len2State s = return ((length s) == 2)
如果你看国家的定义,我们可以看到
状态数据
Int
,以及
Bool
,因此数据构造函数包装的函数必须具有
Int -> (Bool, Int)
. 现在,想象一个无状态版本的
len2State
String -> Bool
. 那么,您将如何将这样一个函数转换为一个返回适合状态包装器的值的函数呢?
Int
表示状态值。它还需要返回一个状态值,另一个
. 因为我们实际上并没有在这个函数中对状态做任何事情,所以让我们做一件显而易见的事情——直接传递int。这是一个状态函数,根据无状态版本定义:
len2 :: String -> Bool
len2 s = ((length s) == 2)
len2State :: String -> (Int -> (Bool, Int))
len2State s i = (len2' s, i)
但这有点愚蠢和多余。让我们概括一下转换,这样我们就可以传入结果值,并将任何内容转换为类似状态的函数。
convert :: Bool -> (Int -> (Bool, Int))
convert r d = (r, d)
len2 s = ((length s) == 2)
len2State :: String -> (Int -> (Bool, Int))
len2State s = convert (len2 s)
如果我们想要一个改变状态的函数呢?很明显,我们不能用
convert
Int
对于新的状态值,当然必须返回一个函数
stateData -> (result, stateData)
结果
值在状态计算之外,所以我们这里的结果是
()
overwriteState :: Int -> (Int -> ((), Int))
overwriteState newState _ = ((), newState)
那很容易!现在,让我们实际上
用那个州的数据。让我们重写一下
len2State
从上面的内容,我们将比较字符串长度和当前状态值。
lenState :: String -> (Int -> (Bool, Int))
lenState s i = ((length s) == i, i)
len
函数需要将状态作为参数,但我们不希望它“了解”状态。真是尴尬。但是,我们可以编写一个快速的助手函数来为我们处理所有事情:我们将为它提供一个需要使用状态值的函数,它将值传入,然后将所有内容打包回一个状态函数
谁也不知道。
useState :: (Int -> Bool) -> Int -> (Bool, Int)
useState f d = (f d, d)
len :: String -> Int -> Bool
len s i = (length s) == i
lenState :: String -> (Int -> (Bool, Int))
lenState s = useState (len s)
现在,棘手的部分--如果我们想把这些函数串在一起呢?假设我们想使用
lenState
结果
chainStates :: (Int -> (result1, Int)) -> (result1 -> (Int -> (result2, Int))) -> (Int -> (result2, Int))
chainStates prev f d = let (r, d') = prev d
in f r d'
现在,有趣的部分:在
chainStates
和
转换
useState
这将返回状态数据作为其结果,这样ChainState就可以将其传递给那些不知道我们正在使用的技巧的函数。此外,我们将使用lambdas接受前面函数的结果,并为它们指定临时名称。好吧,让我们实现这一点:
extractState :: Int -> (Int, Int)
extractState d = (d, d)
chained :: String -> (Int -> (Bool, Int))
chained str = chainStates extractState $ \state1 ->
let check1 = (len str state1) in
chainStates (overwriteState (
if check1
then state1
else state1 * 2)) $ \ _ ->
chainStates extractState $ \state2 ->
let check2 = (len str state2) in
convert (check1 || check2)
并尝试一下:
> chained "abcd" 2
(True, 4)
> chained "abcd" 3
(False, 6)
> chained "abcd" 4
(True, 4)
> chained "abcdef" 5
(False, 10)
当然,我们不能忘记,状态实际上是一个单子,它包装了类似于状态的函数,使我们远离它们,因此我们所构建的漂亮函数中没有一个能帮助我们处理真实的事情。还是会?令人震惊的是,事实证明,真实状态的monad以不同的名称提供了所有相同的功能:
runState (State s) = s
return r = State (convert r)
(>>=) s f = State (\d -> let (r, d') = (runState s) d in
runState (f r) d')
get = State extractState
put d = State (overwriteState d)
声明:
chained str = get >>= \state1 ->
let check1 = (len str state1) in
put (if check1
then state1 else state1 * 2) >>= \ _ ->
get >>= \state2 ->
let check2 = (len str state2) in
return (check1 || check2)
或者,所有这些都用等效do符号表示:
chained str = do
state1 <- get
let check1 = len str state1
_ <- put (if check1 then state1 else state1 * 2)
state2 <- get
let check2 = (len str state2)
return (check1 || check2)