Haskell 状态单元、随机数序列和单元码
我试图掌握状态单子,为此我想编写一个单子代码,使用线性同余生成器生成随机数序列(可能不太好,但我的目的只是学习状态单子,而不是构建一个好的RNG库) 生成器就是这样(为了简单起见,我想生成一系列Haskell 状态单元、随机数序列和单元码,haskell,monads,state-monad,do-notation,Haskell,Monads,State Monad,Do Notation,我试图掌握状态单子,为此我想编写一个单子代码,使用线性同余生成器生成随机数序列(可能不太好,但我的目的只是学习状态单子,而不是构建一个好的RNG库) 生成器就是这样(为了简单起见,我想生成一系列Bools): 不要担心数字,这只是种子的更新规则(根据数字配方)应该生成Ints的伪随机序列。现在,如果我想按顺序生成随机数,我会: rand3Bools :: Seed -> ([Bool], Seed) rand3Bools seed0 = let (b1, seed1) = random
Bool
s):
不要担心数字,这只是种子的更新规则(根据数字配方)应该生成Int
s的伪随机序列。现在,如果我想按顺序生成随机数,我会:
rand3Bools :: Seed -> ([Bool], Seed)
rand3Bools seed0 = let (b1, seed1) = random seed0
(b2, seed2) = random seed1
(b3, seed3) = random seed2
in ([b1,b2,b3], seed3)
好的,我可以通过使用状态单子来避免这个样板:
import Control.Monad.State
data Random {seed :: Seed, value :: Bool}
nextVal = do
Random seed val <- get
let seed' = updateSeed seed
val' = even seed'
put (Random seed' val')
return val'
updateSeed seed = let (a,b,m) = (1664525, 1013904223, 2^32) in (a*seed + c) `mod` m
好的,这很好,给我一个n个伪随机Bool
s的列表,用于每个给定的种子。但是
我可以阅读我所做的(主要基于这个例子:)并复制它来做其他事情。但我不认为我能理解do符号和一元函数(比如replicateM)背后到底发生了什么
有人能帮我解决一些疑问吗
1-我曾尝试对nextVal函数进行去语法处理,以了解它的功能,但我做不到。我可以猜它提取当前状态,更新它,然后将状态传递给下一个计算,但这只是基于阅读这个dosugar,就像它是英语一样
我如何真正地将此函数分解为原始的>>=函数,并逐步返回函数
2-我无法掌握put
和get
函数的确切功能。我可以猜到,他们“打包”和“解包”的状态。但对我来说,做糖背后的机制仍然是难以捉摸的
好的,关于这个代码的任何其他一般性评论都是非常受欢迎的。有时,我觉得Haskell可以创建一个有效的代码并完成我期望它完成的任务,但我不能像我习惯于使用命令式程序那样“遵循评估”。首先,您的示例过于复杂,因为它不需要将
val
存储在状态monad中;只有种子是持久状态。第二,我认为如果您不使用标准的state monad,而是自己用它们的类型重新实现所有state monad及其操作,您的运气会更好。我想这样你会学到更多。以下是一些让您开始学习的声明:
data MyState s a = MyState (s -> (s, b))
get :: Mystate s s
put :: s -> Mystate s ()
然后,您可以编写自己的连接词:
unit :: a -> Mystate s a
bind :: Mystate s a -> (a -> Mystate s b) -> Mystate s b
最后
data Seed = Seed Int
nextVal :: Mystate Seed Bool
至于去糖化的麻烦,您使用的
do
符号非常复杂。
但是去糖是一个一次一行的机械过程。据我所知,您的代码应该这样设计(回到您的原始类型和代码,我不同意):
为了使嵌套结构更加清晰,我对缩进做了很大的改动。状态单子一开始看起来确实有点混乱;让我们按照诺曼·拉姆齐(Norman Ramsey)的建议去做,并了解如何从头开始实施。警告,这太长了 首先,State有两个类型参数:包含的状态数据的类型和计算的最终结果的类型。我们将分别使用
stateData
和result
作为它们的类型变量。如果你仔细想想,这是有道理的;基于状态的计算的定义特征是,它在生成输出时修改状态
不太明显的是,类型构造函数将函数从一个状态转换为一个修改后的状态并得到结果,如下所示:
newtype State stateData result = State (stateData -> (result, stateData))
因此,虽然monad被称为“State”,但由monad包装的实际值是基于状态的计算值,而不是包含状态的实际值
记住这一点,我们不应该惊讶地发现,用于在状态monad中执行计算的函数runState
实际上只是包装函数本身的访问器,可以这样定义:
runState (State f) = f
那么,定义一个返回状态值的函数意味着什么呢?让我们暂时忽略State是monad的事实,只看底层类型。首先,考虑这个函数(实际上不与状态做任何事情):< /P>
如果您查看State的定义,我们可以看到这里的stateData
类型是Int
,而result
类型是Bool
,因此数据构造函数包装的函数必须具有类型Int->(Bool,Int)
。现在,想象一个无状态版本的len2State
——显然,它的类型是String->Bool
。那么,您将如何将这样一个函数转换为一个返回适合状态包装器的值的函数呢
显然,转换后的函数需要第二个参数,一个表示状态值的Int
。它还需要返回一个状态值,另一个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)
,因为这就是我们的状态包装器所需要的。覆盖状态值实际上在状态计算之外没有一个合理的结果
值,因此我们这里的结果将是runState (State f) = f
len2State :: String -> State Int Bool
len2State s = return ((length s) == 2)
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)
overwriteState :: Int -> (Int -> ((), Int))
overwriteState newState _ = ((), newState)
lenState :: String -> (Int -> (Bool, Int))
lenState s i = ((length s) == i, i)
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)
chainStates :: (Int -> (result1, Int)) -> (result1 -> (Int -> (result2, Int))) -> (Int -> (result2, Int))
chainStates prev f d = let (r, d') = prev d
in f r d'
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)
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)
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)
(>>=) :: (s -> (s,a)) ->
(a -> s -> (s,b)) ->
(s -> (s,b))