Haskell STM总是成功的

11

Haskell的stm库中有一个函数,其类型签名如下:

alwaysSucceeds :: STM a -> STM ()

据我理解,Haskell中的STM在执行计算时可能会发生三种“出错”(宽泛地使用这个术语)的情况:

  1. 已读取的TVar的值被另一个线程更改。
  2. 违反了用户指定的不变量。这似乎通常是通过调用retry使其重新开始启动的。这实际上使线程阻塞,然后在读取集合中的TVar更改后重试一次。
  3. 抛出异常。通过调用throwSTM引发异常。这种情况与前两种不同,因为事务不会重新启动。相反,错误会传播并且要么使程序崩溃,要么在IO单子中被捕获。

如果这些准确(如果不是,请告诉我),我就不能理解alwaysSucceeds到底能做什么。看起来构建在其上的always函数可以在没有alwaysSucceeds的情况下编写:

--This is probably wrong
always :: STM Bool -> STM ()
always stmBool = stmBool >>= check
alwaysSucceeds的文档说明如下:

alwaysSucceeds 添加一个新的不变式,该不变式必须在传递给它时为真,在当前事务结束时以及每个随后的事务结束时也必须为真。如果它在任何这些点上失败,则违反它的事务将被中止,并传播不变式引发的异常。

但是,由于参数的类型为STM a(多态的a),因此它不能使用事务返回的值来制定任何决策部分。所以,似乎它会寻找我前面列出的不同类型的故障。但这有什么意义呢?STM monad已经处理了这些故障。将其包装在此函数中会产生什么影响?以及为什么类型为a的变量被删除,导致STM ()

自从你在那里评论提醒我后,我想起来了:有一个当前的 GHC 提案要弃用这个功能 - Ørjan Johansen
1个回答

8
alwaysSucceeds 的特殊作用不在于它如何在运行时检查失败(仅运行“环境”的操作应该做同样的事情),而在于它在事务结束时重新运行不变式检查。基本上,这个函数创建一个用户指定的不变式,就像上面提到的(2),它不仅现在必须保持,而且在以后的事务结束时也必须保持。请注意,“事务”不是指STM单子中的每个子操作,而是指传递给atomically的组合操作。我猜a只是为了方便而被删除,这样你就不必将操作转换为STM()(例如使用void)然后再将其传递给alwaysSucceeds。返回值对于以后的重复检查无用。

明白了。《Real World Haskell》教程的第28章提供了一个相当不错的例子。所以,如果我理解正确的话,alwaysSucceedsalways都应该在可能调用throwSTM的某些内容上调用。此外,由于它们在每个事务之后重新运行,因此使用大量这些函数似乎会显著影响性能。 - Andrew Thaddeus Martin
经过进一步测试,我认为它不必是throwSTM,似乎可以基于retry(包括check)实现,但是如果您的不变量失败,则事务将无限重试,除非您在另一个线程中有某些操作最终使其成功。 - Ørjan Johansen
1
@AndrewThaddeusMartin 一个有启示性的测试是:do t <- newTVarIO 0; atomically . alwaysSucceeds $ readTVar t >>= check . (>= 0); forkIO $ forever (do threadDelay 10000000; atomically $ modifyTVar t (max 1)); replicateM_ 3 . atomically . modifyTVar t $ subtract 1 - Ørjan Johansen
我运行了测试。你很聪明,它终于在30秒后成功了,这是有道理的。虽然我觉得这种使用方式似乎有点奇怪,让推理更加困难。此外,你可以编写像 f = alwaysSucceeds . alwaysSucceeds . alwaysSucceeds 这样的函数,这似乎有点奇怪。感觉将结果放在 IO monad 中对于 alwaysSucceeds 更有意义。 - Andrew Thaddeus Martin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接