问题 为什么GHCI在出错后会陷入错误状态?


首先,我为非描述性标题道歉。由于我不知道究竟发生了什么,所以我无法让它更具体。

现在我的问题。我已经针对问题23实现了以下代码段 99个Haskell问题,应该随机选择 n 列表中的项目:

rndSelect' :: RandomGen g => [a] -> Int -> g -> ([a], g)
rndSelect' _ 0 gen = ([], gen)
rndSelect' [] _ _ = error "Number of items requested is larger than list"
rndSelect' xs n gen = ((xs !! i) : rest, gen'')
                    where (i, gen') = randomR (0, length xs - 1) gen
                          (rest, gen'') = (rndSelect' (removeAt xs i) (n - 1) gen')

rndSelectIO' :: [a] -> Int -> IO [a]
rndSelectIO' xs n = getStdRandom $ rndSelect' xs n

removeAt :: [a] -> Int -> [a]
removeAt xs n
  | length xs <= n || n < 0 = error "Index out of bounds"
  | otherwise = let (ys, zs) = splitAt n xs
                    in ys ++ (tail zs)

现在当我加载它 ghci 这适用于有效参数:

*Main> rndSelectIO' "asdf" 2 >>= putStrLn 
af

但是,当我使用超出范围的索引时会发生奇怪的事情:

*Main> rndSelectIO' "asdf" 5 >>= putStrLn
dfas*** Exception: Number of items requested is larger than list
*Main> rndSelectIO' "asdf" 2 >>= putStrLn
*** Exception: Number of items requested is larger than list

如您所见,以下2(对我来说)意外事情发生:

  1. 它不是直接给出错误,而是首先打印输入的排列。
  2. 在它发出一次错误后,它将不再执行。

我怀疑1.与懒惰评估有关,但我完全不知道为什么2.发生。这里发生了什么?


12087
2018-06-12 11:38


起源

交互式会话中的每一行都在同一个隐含的内部 do 语句,永远不会结束,所以每一行都将结果传递给下一行。 - chepner


答案:


getStdRandom 功能基本上查找了一个 StdGen 全局变量中的值,在其上运行一些函数,将新种子放回全局变量,并将结果返回给调用者。

如果有问题的函数返回错误,则该错误将被放入全局变量中。现在,所有使用此全局变量的尝试都将引发异常。 (一世 告诉 你的全局变量是邪恶的! ;-)

试着打电话 getStdGen 手动自己。它将打印出当前的随机种子,或抛出异常。如果它抛出异常......那就是你的问题。

我相信你可以用 setStdGen 重置的东西。


13
2018-06-12 11:48



所以这是一个懒惰的问题吧? getStdRandom 不应该把错误放回去 StdGen。 - mariop
@mariop可以说允许一个单一的全局变量首先是一个黑客攻击(例如,线程安全可能受到威胁)。但是,是的,我想你可以说它应该做一些事情,以避免将异常重新插入全局变量...... - MathematicalOrchid
在现实世界的应用程序中如何处理?每个人都会一直初始化它自己的随机发生器吗?这是一个刻意的设计决定,还是一个不能捕捉错误的疏忽 getStdRandom? - Tiddo
你通常不会使用 error 在现实世界的应用程序你会回来的 Maybe 要么 Either 相反的价值。在实际应用中,您通常也会使用其他一些随机源 StdGen。 - shang
理想情况下,您可以管理自己的随机种子,而不是依赖于整个程序中所有代码之间共享的隐藏全局变量。 (和 System.Random 一般来说效率不高;还有其他的库,实施Mersenne-twister或MWC或其他。) - MathematicalOrchid