如何解决Purescript 中遍历/绑定/折叠绑定效果之间的差异
我一直在努力解决这个问题,我编写了四个我希望运行相同的函数,我很好奇它们为什么不同。
git checkout HEAD~2 "Finished 1 stage"
对于前两个,控制台似乎会在发生时显示每个效果。日志语句之间可能有 1/2 秒以上的延迟。我会非常惊讶地看到这些行为有所不同,因为我知道 main2 中的 do 符号只是 toEffect :: Tuple Int String -> Effect Unit
toEffect (Tuple i strng) =
log $ append (show i <> ": ") $
statefulPuzzletoString $
selectFirstLadderBruteForce $
parsePuzzle strng
main1 :: Effect Unit
main1 = (toEffect $ Tuple 1 $ fromMaybe "" $ hardestBoardStringsX11 !! 0) >>=
(\_ -> toEffect $ Tuple 2 $ fromMaybe "" $ hardestBoardStringsX11 !! 1) >>=
(\_ -> toEffect $ Tuple 3 $ fromMaybe "" $ hardestBoardStringsX11 !! 2) >>=
(\_ -> toEffect $ Tuple 4 $ fromMaybe "" $ hardestBoardStringsX11 !! 3)
-- ... Pattern Could continue for all 11 boards
main2 :: Effect Unit
main2 = do
toEffect $ Tuple 1 $ fromMaybe "" $ hardestBoardStringsX11 !! 0
toEffect $ Tuple 2 $ fromMaybe "" $ hardestBoardStringsX11 !! 1
toEffect $ Tuple 3 $ fromMaybe "" $ hardestBoardStringsX11 !! 2
toEffect $ Tuple 4 $ fromMaybe "" $ hardestBoardStringsX11 !! 3
-- ... Pattern Could continue for all 11 boards
main3 :: Effect Unit
main3 = foldl
(\acc new -> acc >>= \_ -> new)
(pure unit)
effects
where
effects :: Array (Effect Unit)
effects = map toEffect $ mapWithIndex Tuple hardestBoardStringsX11
main4 :: Effect Unit
main4 = traverse_ toEffect $ mapWithIndex Tuple hardestBoardStringsX11
后两个同时出现记录他们的陈述。
我对 main1
并不完全确定,但我非常有信心 main4
的行为确实与前两个相同。
对这里发生的事情有任何了解吗?
解决方法
main3
和 main4
的行为都是出于同样的原因,原因在于 evaluation 和 execution 之间的区别。 >
当您有一个 Effect a
类型的值时,它表示产生 a
的某种效果,大概是您从某个地方获得了该值。让我们说:
myEffect = makeMeAnEffect "foo"
此值已在 makeMeAnEffect
内求值,但尚未执行。在这里,“评估”意味着进行任何必要的计算以产生类型 Effect a
的值。创建此值可能涉及一些计算 - 例如数字相乘、字符串遍历、矩阵相加。这就是所有的“评估”。
但是评估的结果是对执行效果时应该发生什么的“描述”。这里“执行”的意思是“运行”效果,让它发生任何它描述的动作。
评估和执行在技术上是独立的概念。许多语言将它们混为一谈,但纯函数式语言,例如 PureScript 和 Haskell,保持严格的分离:首先创建应该发生什么的描述(“评估”),然后“运行”该描述(“执行”)。
这种区别在实践中非常重要:“评估”是纯粹的,这意味着除了结果之外它是完全不可观察的,因此编译器可以对它做任何想做的事情 - 例如优化、滚动/展开,甚至完全丢弃——只要结果保持不变。另一方面,“执行”必须按照程序员指定的确切方式执行,因为它的全部意义在于产生效果,因此弄乱它会产生明显的后果。
在您的特定情况下,在 toEffect
的主体中,评估是 log $
之后发生的一切。所有对 append
、selectFirstLadderBruteForce
等的调用 - 所有这些都是“评估”。这些都没有效果。您正在执行一些计算,以确定您将创建什么样的效果。
然后,一旦你完成了所有的计算,你将它的结果传递给 log
,这使你成为一个 Effect Unit
,它是“应该发生什么的描述”。在这种特殊情况下,“应该发生什么”非常小 - 只需向控制台写入一个字符串即可。
现在,我们终于可以了解 main1
/main2
和 main3
/main4
之间的区别了。
在 main1
和 main2
中,您仅在第一个效果执行后创建每个下一个效果。所以评估和执行“重叠”,可以这么说:首先你做第一个评估,创建第一个效果,然后运行它,然后,只有在它运行完成后,你才能进行第二个评估并创建第二个效果。等等。由于昂贵的部分(在您的情况下)是评估,因此每次下一次执行最终都会延迟下一次评估所需的时间。
另一方面,在 main3
和 main4
中,您首先进行评估,通过在数组上调用 map toEffect
一次创建所有效果,然后才继续执行它们逐个。而且,由于再次评估(在您的情况下)是昂贵的部分,并且所有这些都在开始时发生,因此执行不会延迟。每个效果都非常小(只需打印到控制台),因此它们都执行得非常快。
如果你真的想在上一次执行完成之前阻止下一次评估发生,你可以使用这个技巧:在 pure unit
的开头添加一个 toEffect
像这样:
toEffect (Tuple i strng) = do
pure unit
log $ append (show i <> ": ") $
statefulPuzzleToString $
selectFirstLadderBruteForce $
parsePuzzle strng
这将确保第二行在第一行执行之前不会开始评估,从而使每个评估只在其各自的执行之前发生。
最后,另一个有趣的事实:在 Haskell 中,相同的程序会以不同的方式工作,因为 Haskell 是懒惰的。当被要求进行评估时,它不会立即进行评估,而只是“记住”它已被要求进行评估。并且只有当评估的结果实际上是必要的(这会在执行时发生)时,才会执行。
另一方面,PureScript 是严格的,这意味着它总是立即计算所有内容。在这种特殊情况下,这意味着它会在将结果传递给 append
之前计算整个 log
等系列调用。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。