运行 多义词中的 NonDet 效果一次

Running the NonDet effect once in Polysemy

我对 Polysemy 比较陌生,我正在努力思考如何正确使用 NonDet。具体来说,假设我有这个计算

generate :: Member NonDet r => Sem r Int
generate = msum $ fmap pure [0..]

computation :: (Member NonDet r, Member (Final IO) r) => Sem r ()
computation = do
  n <- generate
  guard (n == 100)
  embedFinal $ print n

打印数字 100 的效率极低,但它证明了我遇到的问题。现在,我想运行这个效果只限于获得第一个成功。也就是说,我想 运行 这个效果足够长的时间来“找到”数字 100 并打印出来,然后我想停止。

我的第一次尝试

attempt1 :: IO ()
attempt1 = void . runFinal . runNonDet @[] $ computation

这个短路失败。它打印 100 但随后永远挂起,再次寻找数字 100。这就说得通了;毕竟,我实际上并没有告诉它我只想要一个解决方案。那么让我们试试吧。

我的第二次尝试

runNonDetOnce :: Sem (NonDet ': r) a -> Sem r (Maybe a)
runNonDetOnce = fmap listToMaybe . runNonDet

attempt2 :: IO ()
attempt2 = void . runFinal . runNonDetOnce $ computation

我们在这里所做的就是丢弃除列表头部以外的所有内容。可以理解,这并没有改变任何东西。 Haskell 已经没有评估列表,因此丢弃未使用的值不会改变任何内容。与 attempt1 一样,此解决方案在打印 100 后永远挂起。

我的第三次尝试

attempt3 :: IO ()
attempt3 = void . runFinal . runNonDetMaybe $ computation

所以我尝试使用 runNonDetMaybe。不幸的是,这个没有打印任何东西就退出了。弄清楚为什么会这样,但我有一个理论。文档说

Unlike runNonDet, uses of <|> will not execute the second branch at all if the first option succeeds.

所以基本上是贪心的,成功了不回头。因此,运行我的计算是这样的。

computation = do
  n <- generate        -- Ah yes, n = 0. Excellent!
  guard (n == 100)     -- Wait, 0 /= 100! Failure! We can't backtrack, so abort.
  embedFinal $ print n

无解

在这个小例子中,我们可以稍微改变一下计算,就像这样

computation :: (Member NonDet r, Member (Final IO) r) => Sem r ()
computation = msum $ fmap (\n -> guard (n == 100) >> embedFinal (print n)) [0..]

因此,我们不是生成一个数字然后稍后检查它,而是简单地将 generate 移动到 computation 中。有了这个 computationattempt3 就成功了,因为我们可以在不回溯的情况下得到“正确”的答案。这在这个小例子中有效,但对于更大的代码库来说是不可行的。除非有人有避免回溯的良好系统方法,否则我看不到将此解决方案推广到大型程序中跨越多个文件的计算的好方法。

另一个非解决方案是使用IO作弊。

computation :: (Member NonDet r, Member (Final IO) r) => Sem r ()
computation = do
  n <- generate
  guard (n == 100)
  embedFinal $ print n
  embedFinal $ exitSuccess

现在attempt1attempt2成功了,因为我们只是在成功后强行退出程序。但是,除了感觉非常草率之外,这也不能一概而论。我想在找到 100 之后停止 运行ning current 计算,而不是整个程序。

因此,总而言之,我希望上面第一个代码片段中给出的计算 运行 以某种方式使用 Polysemy 导致它回溯(在 NonDet 中)直到找到一个成功值(在上面的例子中,n = 100)然后停止运行宁副作用并结束计算。我尝试深入研究 runNonDetMaybe 和 co 的源代码,希望能够重现具有我想要的效果的类似它的东西,但我的 Polysemy 技能远未达到理解所有 Weavingdecomp 恶作剧发生在那里。我希望这里比我更了解这个库的人可以为我指明正确的方向,使 运行 宁 NonDet 达到预期的效果。

Now attempt1 and attempt2 succeed, since we simply forcibly exit the program after success. But, aside from feeling incredibly sloppy, this doesn't generalize either. I want to stop running the current computation after finding 100, not the whole program.

而不是 exitSuccess,一个密切相关的想法是抛出一个您可以在解释器中捕获的异常。