如何在 Haskell 中以隐藏方式初始化状态(就像 PRNG 那样)?

发布于 2024-07-26 19:30:12 字数 1138 浏览 5 评论 0原文

我浏览了一些关于 State monad 的教程,我想我明白了。

例如,如 这个不错的教程

import Data.Word

type LCGState = Word32

lcg :: LCGState -> (Integer, LCGState)
lcg s0 = (output, s1) 
  where s1 = 1103515245 * s0 + 12345
        output = fromIntegral s1 * 2^16 `div` 2^32


getRandom :: State LCGState Integer
getRandom = get >>= \s0 -> let (x,s1) = lcg s0
                           in put s1 >> return x

好的,所以我可以使用 getRandom :

*Main> runState getRandom 0
(0,12345)
*Main> runState getRandom 0
(0,12345)
*Main> runState getRandom 1              
(16838,1103527590)

但是我每次调用时仍然需要将种子传递给 PRNG。 我知道 Haskell 实现中可用的 PRNG 不需要这样:

Prelude> :module Random
Prelude Random> randomRIO (1,6 :: Int)
(...) -- GHC prints some stuff here
6
Prelude Random> randomRIO (1,6 :: Int)
1

所以我可能误解了 State monad,因为我在大多数教程中都可以看到 似乎不是“持久”状态,而只是线程状态的一种便捷方式。

那么...我怎样才能拥有自动初始化的状态(可能来自某些 使用时间和其他不太可预测的数据的函数),例如 Random 模块 做?

多谢!

I went through some tutorials on the State monad and I think I got the idea.

For example, as in this nice tutorial:

import Data.Word

type LCGState = Word32

lcg :: LCGState -> (Integer, LCGState)
lcg s0 = (output, s1) 
  where s1 = 1103515245 * s0 + 12345
        output = fromIntegral s1 * 2^16 `div` 2^32


getRandom :: State LCGState Integer
getRandom = get >>= \s0 -> let (x,s1) = lcg s0
                           in put s1 >> return x

OK, so I can use getRandom:

*Main> runState getRandom 0
(0,12345)
*Main> runState getRandom 0
(0,12345)
*Main> runState getRandom 1              
(16838,1103527590)

But I still need to pass the seed to the PRNG every time I call it. I know that the
PRNG available in Haskell implementations does not need that:

Prelude> :module Random
Prelude Random> randomRIO (1,6 :: Int)
(...) -- GHC prints some stuff here
6
Prelude Random> randomRIO (1,6 :: Int)
1

So I probably misunderstood the State monad, because what I could see in most tutorials
doesn't seem to be "persistent" state, but just a convenient way to thread state.

So... How can I have state that is automatically initialized (possible from some
function that uses time and other not-very-predictable data), like the Random module
does?

Thanks a lot!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

独守阴晴ぅ圆缺 2024-08-02 19:30:12

randomRIO 使用 IO monad。 这似乎在解释器中工作得很好,因为解释器也在 IO monad 中工作。 这就是您在示例中看到的; 实际上,您无法在代码的顶层执行此操作 - 无论如何,您都必须将其放入 do 表达式中,就像所有 monad 一样。

在一般代码中,您应该避免使用 IO monad,因为一旦您的代码使用了 IO monad,它就永远与外部状态绑定在一起——您无法摆脱它(即,如果您有使用 IO monad 的代码,则任何代码调用它也必须使用 IO monad;没有安全的方法来“摆脱”它)。 因此 IO monad 应该只用于访问外部环境等绝对需要的事情。

对于本地独立状态之类的东西,您不应该使用 IO monad。 您可以像您提到的那样使用 State monad,也可以使用 ST monad。 ST monad 包含许多与 IO monad 相同的功能; 即存在STRef可变单元,类似于IORef。 与 IO 相比,ST 的好处在于,当您完成后,您可以在 ST monad 上调用 runST 来从 monad 中获取计算结果,而这是您无法做到的IO。

至于“隐藏”状态,这只是 Haskell 中 monad 的 do 表达式语法的一部分。 如果您认为需要显式传递状态,那么您就没有正确使用 monad 语法。

这是在 IO Monad 中使用 IORef 的代码:

import Data.IORef
foo :: IO Int -- this is stuck in the IO monad forever
foo = do x <- newIORef 1
         modifyIORef x (+ 2)
         readIORef x
-- foo is an IO computation that returns 3

这是使用 ST monad 的代码:

import Control.Monad.ST
import Data.STRef
bar :: Int
bar = runST (do x <- newSTRef 1
                modifySTRef x (+ 2)
                readSTRef x)
-- bar == 3

代码的简单性本质上是相同的; 只不过在后一种情况下,我们可以从 monad 中获取值,而在前一种情况下,我们不能不将其放入另一个 IO 计算中。

randomRIO uses the IO monad. This seems to work nicely in the interpreter because the interpreter also works in the IO monad. That's what you are seeing in your example; you can't actually do that at the top-level in code -- you would have to put it in a do-expression like all monads anyway.

In general code you should avoid the IO monad, because once your code uses the IO monad, it is tied to external state forever -- you can't get out of it (i.e. if you have code that uses the IO monad, any code that calls it also has to use the IO monad; there is no safe way to "get out" of it). So the IO monad should only be used for things like accessing the external environment, things where it is absolutely required.

For things like local self-contained state, you should not use the IO monad. You can use the State monad as you have mentioned, or you can use the ST monad. The ST monad contains a lot of the same features as the IO monad; i.e. there is STRef mutable cells, analogous to IORef. And the nice thing about ST compared to IO is that when you are done, you can call runST on an ST monad to get the result of the computation out of the monad, which you can't do with IO.

As for "hiding" the state, that just comes as part of the syntax of do-expressions in Haskell for monads. If you think you need to explicitly pass the state, then you are not using the monad syntax correctly.

Here is code that uses IORef in the IO Monad:

import Data.IORef
foo :: IO Int -- this is stuck in the IO monad forever
foo = do x <- newIORef 1
         modifyIORef x (+ 2)
         readIORef x
-- foo is an IO computation that returns 3

Here is code that uses the ST monad:

import Control.Monad.ST
import Data.STRef
bar :: Int
bar = runST (do x <- newSTRef 1
                modifySTRef x (+ 2)
                readSTRef x)
-- bar == 3

The simplicity of the code is essentially the same; except that in the latter case we can get the value out of the monad, and in the former we can't without putting it inside another IO computation.

顾忌 2024-08-02 19:30:12
secretStateValue :: IORef SomeType
secretStateValue = unsafePerformIO $ newIORef initialState
{-# NOINLINE secretStateValue #-}

现在使用正常的 readIORef 和 writeIORef 访问您的 SecretStateValue,在 IO 单子中。

secretStateValue :: IORef SomeType
secretStateValue = unsafePerformIO $ newIORef initialState
{-# NOINLINE secretStateValue #-}

Now access your secretStateValue with normal readIORef and writeIORef, in the IO monad.

空城仅有旧梦在 2024-08-02 19:30:12

所以我可能误解了 State monad,因为我在大多数教程中看到的似乎不是“持久”状态,而只是线程状态的一种便捷方式。

状态 monad 正是关于在某个范围内线程化状态。

如果您想要顶级状态,那是在语言之外的(并且您必须使用全局可变变量)。 请注意,这可能会使代码的线程安全变得复杂——该状态是如何初始化的? 什么时候? 以及通过哪个线程?

So I probably misunderstood the State monad, because what I could see in most tutorials doesn't seem to be "persistent" state, but just a convenient way to thread state.

The state monad is precisely about threading state through some scope.

If you want top level state, that's outside the language (and you'll have to use a global mutable variable). Note how this will likely complicated thread safety of your code -- how is that state initialized? and when? And by which thread?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文