我对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
。不幸的是,这个退出时没有打印任何内容。弄清楚为什么会花点时间,但是我有一个理论。文档说
与runNonDet不同,如果第一个选项成功,使用<|>根本不会执行第二个分支。
基本上,它很贪心,成功后不会回溯。因此,它像这样运行我的计算。
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
内即可。有了这个computation
,attempt3
成功了,因为我们可以得到“正确”的答案而无需回溯。这在这个小示例中有效,但是对于较大的代码库则不可行。除非有人有很好的系统方法来避免回溯,否则我看不出将这种解决方案推广到大型程序中跨越多个文件的计算的好方法。
另一种非解决方案是使用IO
作弊。
computation :: (Member NonDet r, Member (Final IO) r) => Sem r ()
computation = do
n <- generate
guard (n == 100)
embedFinal $ print n
embedFinal $ exitSuccess
现在attempt1
和attempt2
成功了,因为我们仅在成功后强行退出程序。但是,除了感到难以置信的草率之外,这也不能一概而论。我想在找到100个而不是整个程序之后停止运行 current 计算。
因此,总而言之,我希望上面的第一个代码段中给出的计算以某种方式使用Polysemy运行,以使其回溯(在NonDet
中),直到找到一个成功的值为止(在示例中)以上n = 100
),然后停止运行副作用并结束计算。我试图深入研究runNonDetMaybe
的源代码,并希望能够重现与我想要的效果类似的东西,但是我的一词多义技能并不能完全理解所有{ {1}}和Weaving
恶作剧在那里发生。我希望这里的人比我拥有更多关于此库的专业知识,可以为我指明正确的方向,以达到预期的效果运行decomp
。
答案 0 :(得分:1)
现在try1和try2成功了,因为我们只是在成功后强行退出程序。但是,除了感到难以置信的草率之外,这也不能一概而论。我想在找到100个而不是整个程序之后停止运行当前的计算。
与exitSuccess
密切相关的想法是引发一个可以在解释器中捕获的异常。