【问题标题】:Simple Haskell IORef - "Couldn't match type `IO Int' with `Int'" - can't see how it's different简单的 Haskell IORef - “无法将类型 `IO Int' 与 `Int' 匹配” - 看不出它有何不同
【发布时间】:2014-05-12 19:39:29
【问题描述】:

我正在尝试在 Haskell 中创建一个简单的随机数生成器,现在使用 IORef 来存储可变变量。这个想法是我可以初始化种子,然后根据种子生成数字,并将新种子存储为下一个随机整数。

我得到的完整错误是:

random2.hs:9:17:
    Couldn't match type `IO Int' with `Int'
    Expected type: IO (IORef Integer)
                   -> (IORef Integer -> IO Int) -> Int
      Actual type: IO (IORef Integer)
                   -> (IORef Integer -> IO Int) -> IO Int
    In a stmt of a 'do' block: seed <- newIORef 7
    In the expression:
      do { seed <- newIORef 7;
           randomGen (readIORef seed) }
    In an equation for `getRandom':
        getRandom
          = do { seed <- newIORef 7;
                 randomGen (readIORef seed) }

random2.hs:10:17:
    Couldn't match type `(,) Int' with `IO'
    Expected type: IO Int
      Actual type: (Int, Int)
    In the return type of a call of `randomGen'
    In a stmt of a 'do' block: randomGen (readIORef seed)
    In the expression:
      do { seed <- newIORef 7;
           randomGen (readIORef seed) }

random2.hs:10:28:
    Couldn't match expected type `Int' with actual type `IO Integer'
    In the return type of a call of `readIORef'
    In the first argument of `randomGen', namely `(readIORef seed)'
    In a stmt of a 'do' block: randomGen (readIORef seed)
Failed, modules loaded: none.

我不明白它为什么不能匹配类型 - 我明确表示 randomGen 接受/返回一个 Int。这是我的代码:

module Main where
    import Data.IORef

    randomGen :: Int -> (Int, Int)
    randomGen x = (x,x+1)

    getRandom :: Int
    getRandom = do
        seed <- newIORef 7
        randomGen (readIORef seed)

知道这里发生了什么吗?

谢谢,

更新代码:

module Main where
    import Data.IORef
    import Control.Monad

    randomGen :: Int -> (Int, Int)
    randomGen x = (x,x+1)

    getRandom :: IO Int
    getRandom = do
        seed <- newIORef 7
        liftM (fst (randomGen (readIORef seed)))

【问题讨论】:

  • randomGen 是纯的,但 readIORef 返回 IO a。在 do 子句中,您将在 randomGen 前面使用 liftM。还有getRandom :: IO Int。一旦它是 IO,你就无法摆脱它。
  • @dumb0 所以我添加了导入 Control.Monad,将 getRandom :: Int 的 def 更改为 getRandom :: IO Int 并将最后一行从 randomGen (readIORef seed) 更改为 liftM (randomGen (readIORef seed)) - 这就是你所描述的?因为这会引发另一个错误。
  • 我的评论有错误:getRandom :: IO (Int,Int)
  • @dumb0 仍然是一个错误 - Couldn't match expected type IO (Int, Int)' 实际类型为 m0 a10 -&gt; m0 r0'
  • 新错误出现在哪几行?

标签: variables haskell random ioref


【解决方案1】:

IO IntInt 类型在 Haskell 中完全不同。这适用于该表单的任何其他类型,例如 Maybe IntEither String Int。这是 Haskell 类型系统设计的一部分,使它如此强大。您可以将这种形式的任何东西视为一种容器,它在该类型上进行了参数化。因此你可以做类似的事情

getRandom :: IO Int
getRandom = do
    seed <- newIORef 7           -- IO (IORef Int)
    g <- readIORef seed          -- IO Int
    let (x, newG) = randomGen g  -- (Int, Int)
    writeIORef seed newG         -- IO ()
    return x                     -- IO Int

但是,这将始终返回相同的值,因为种子在每次调用后都会被丢弃。我很好奇你为什么要采用这种方法来生成随机数,因为 MonadRandom 包中有这么好的 API。有关如何使用 Rand monad 的示例,请参阅 this 答案我写了一段时间,并查看 this 答案以更深入地解释它的工作原理。

【讨论】:

  • 我采用这种方法是因为我认为我了解 monad,并被告知要通过尝试编写一个包含一个函数的应用程序来测试这一点,我可以调用该函数来获取基于种子的随机数我最初提供了(即防止用户不得不不断更新他们的种子)。我的例子显然是微不足道的(随机数是种子,而种子只是递增)。
  • 所以我有没有办法阻止种子 IORef 每次调用都被丢弃,所以它在函数调用之间仍然存在?
  • 我在这里读到有一个全局命名空间,我可以将变量存储在我的应用程序中吗? hackage.haskell.org/package/global-variables-1.0.1.1/docs/…
  • @SamHeather 是,但 Haskell 中的全局可变变量仅作为最后的手段使用。获取所需内容的自然方法总是从函数中返回它。如果你想要一个涉及 monad 的练习,我建议你尝试使用 State 包装 getRandom 并有一种更方便的方法来更新计算之间的种子。
  • @SamHeather 使用您链接到的包,您将在顶层使用declareIORef,这将使您的程序中的任何IO 函数神奇地使用IORef(我说“神奇地“因为那个包裹包裹着一个臭名昭著的丑陋黑客,所以用它来射击自己的脚更难)。但是,我坚持认为,如果您想使用全局变量来测试您对 monad 的理解,那将是错误的。单子/= IO。
【解决方案2】:

试试:

module Main where
import Data.IORef
import Control.Monad
import Data.Tuple(fst,snd)

randomGen :: Int -> (Int, Int)
randomGen x = (x,x+1)

getRandom :: IO Int -> IO (Int,Int)
getRandom x = do
    y <- x
    seed <- newIORef y
    liftM randomGen $ readIORef seed

此时,在 getRandom 的输出上使用liftM fst 来获取随机数,并使用liftM snd 来获取下一次调用的种子......哦,顺便说一句,System.Randomrandoms 来生成无限随机数列表(或Random 实例的任何其他内容)。重新发明轮子没有意义。

【讨论】:

  • 太棒了,这适用于上述情况。但是因为它现在使用的是liftM,所以我不能在元组中保存第二个值,对吧?我想将它保存回 IORef 以便下一次调用 getRandom 返回一个不同的随机数,但这在当前结构中是不可能的吗?打算用 updateIORef
  • @SamHeather 您无法避免返回种子并将其传递给下一个调用。 getRandom 中的 IORef 是函数的局部变量,因此您必须以任何方式返回种子,因此使用 IORef 会使您的生成器变得不纯,并且什么也得不到。如果你想让种子隐式传递,一个更好的方法是使用 State monad。
  • @dumb0 我不是想重新发明轮子,只是学习。我试图在 Haskell 中提出一些非常简单的有状态程序示例(this),但即使这个实现似乎也不是有状态的,因为每次运行它都会返回 7,从不改变(递增)我想要的 - 我的误解我认为。你有什么办法让它做到这一点,或者我可以看看一个不同的简单有状态问题的想法?
  • 好吧,我的错。根据我对 Haskell 的有限经验,没有状态。一旦 getRandom 返回,它就完成了。如果您使用相同的值(例如return 4)调用getRandom,那么您将获得相同的输出。我所做的是创建一个输出,为您下次调用getRandom 时提供随机数和种子(例如(1,4),其中1 是随机数,4 是您调用@987654334 时使用的数字@ 再次)。这在某种意义上模拟了状态。希望对您有所帮助...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-03-16
  • 2012-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-24
  • 2014-10-17
相关资源
最近更新 更多