Purescript 中 Traversing/Binding/Fold-Binding 效果之间的差异
Differences between Traversing/Binding/Fold-Binding Effects in Purescript
我一直在努力解决这个问题,我已经编写了四个函数,我希望它们 运行 相同,我很好奇它们为什么不同。
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
对于前两个,控制台似乎会显示每个效果。日志语句之间可能有超过 1/2 秒的延迟。我会非常惊讶地看到它们的行为不同,因为我知道 main2 中的 do 符号只是 main1
中所写内容的语法糖
后两个出现同时记录他们的语句。
我不完全确定 main4
,但我非常有信心 main3
真的应该和前两个一样。
对这里发生的事情有任何了解吗?
main3
和 main4
出于同样的原因都会这样做,原因是 评估 和 执行之间的差异.
当你有一个 Effect a
类型的值时,它表示产生 a
的某种效果,大概你从某个地方得到了那个值。比方说:
myEffect = makeMeAnEffect "foo"
此值已在 makeMeAnEffect
中 求值 ,但尚未 执行 。在这里,“评估”意味着进行任何必要的计算以产生类型 Effect a
的值。创建此值可能涉及一些计算 - 例如数字相乘、字符串遍历、矩阵相加。这就是所有的“评价”。
但是评估的结果是对 执行 时应该发生什么的“描述”。这里“执行”的意思是“运行产生效果,使它描述的任何动作发生。
评估和执行在技术上是不同的概念。许多语言将它们混为一谈,但是纯函数式语言,例如 PureScript 和 Haskell,保持严格的分离:首先创建对应该发生的事情的描述(“评估”),然后是“运行”描述(“执行”)。
这种区别在实践中非常重要:“求值”是纯粹的,这意味着除了结果之外它是完全不可观察的,因此编译器可以用它做任何它想做的事——例如优化,roll/unroll,甚至完全删除,- 只要其结果保持不变。另一方面,“执行”必须按照程序员指定的确切方式执行,因为它的全部意义在于产生效果,所以弄乱它会产生明显的后果。
在您的特定情况下,在 toEffect
的正文中,评估是 log $
之后发生的一切。所有对 append
、selectFirstLadderBruteForce
的调用,等等,所有这些都是“评估”。 None 是有效的。您正在执行一些计算以弄清楚您将要创建什么样的效果。
然后,一旦你完成了所有的计算,你将它的结果传递给 log
,这使你成为一个 Effect Unit
,这是“应该发生什么的描述”。在这种特殊情况下,“应该发生什么”非常小——只需向控制台写入一个字符串。
现在,我们终于可以了解 main1
/main2
和 main3
/main4
之间的区别了。
在 main1
和 main2
中,您仅在执行第一个效果后才创建每个下一个效果。所以评估和执行“重叠”,可以这么说:首先你做拳头评估,创造拳头效果,然后你 运行 它,然后,只有在它完成后 运行ning,你才继续做二次评价,创造二次效应。等等。由于昂贵的部分(在您的情况下)是评估,因此每次下一次执行都会延迟下一次评估花费的时间。
另一方面,在 main3
和 main4
中,您首先进行评估,通过在数组上调用 map toEffect
立即创建所有效果,然后才继续一个一个执行。同样,由于评估(在您的情况下)是昂贵的部分,并且所有这些都在开始时发生,因此执行不会延迟。每个效果都非常小(只是打印到控制台),所以它们都执行得非常快。
如果你真的想在上一次执行完成之前阻止下一次评估发生,你可以这样做:在 toEffect
的开头添加一个 pure unit
像这样:
toEffect (Tuple i strng) = do
pure unit
log $ append (show i <> ": ") $
statefulPuzzleToString $
selectFirstLadderBruteForce $
parsePuzzle strng
这将确保在第一行 执行 之前,第二行不会开始 评估 ,从而使每个评估都发生仅在其各自执行之前。
最后,还有一个有趣的事实:在 Haskell 中,相同的程序会以不同的方式运行,因为 Haskell 是惰性的。当被要求进行评估时,它不会立即执行,而只是“记住”它被要求这样做。并且只有当评估的结果确实是必要的时候(这会在执行时发生),才会执行。
另一方面,PureScript 是严格的,这意味着它总是会立即计算所有内容。在这种特殊情况下,这意味着它将计算整个 append
等系列调用,然后才能将其结果传递给 log
.
我一直在努力解决这个问题,我已经编写了四个函数,我希望它们 运行 相同,我很好奇它们为什么不同。
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
对于前两个,控制台似乎会显示每个效果。日志语句之间可能有超过 1/2 秒的延迟。我会非常惊讶地看到它们的行为不同,因为我知道 main2 中的 do 符号只是 main1
后两个出现同时记录他们的语句。
我不完全确定 main4
,但我非常有信心 main3
真的应该和前两个一样。
对这里发生的事情有任何了解吗?
main3
和 main4
出于同样的原因都会这样做,原因是 评估 和 执行之间的差异.
当你有一个 Effect a
类型的值时,它表示产生 a
的某种效果,大概你从某个地方得到了那个值。比方说:
myEffect = makeMeAnEffect "foo"
此值已在 makeMeAnEffect
中 求值 ,但尚未 执行 。在这里,“评估”意味着进行任何必要的计算以产生类型 Effect a
的值。创建此值可能涉及一些计算 - 例如数字相乘、字符串遍历、矩阵相加。这就是所有的“评价”。
但是评估的结果是对 执行 时应该发生什么的“描述”。这里“执行”的意思是“运行产生效果,使它描述的任何动作发生。
评估和执行在技术上是不同的概念。许多语言将它们混为一谈,但是纯函数式语言,例如 PureScript 和 Haskell,保持严格的分离:首先创建对应该发生的事情的描述(“评估”),然后是“运行”描述(“执行”)。
这种区别在实践中非常重要:“求值”是纯粹的,这意味着除了结果之外它是完全不可观察的,因此编译器可以用它做任何它想做的事——例如优化,roll/unroll,甚至完全删除,- 只要其结果保持不变。另一方面,“执行”必须按照程序员指定的确切方式执行,因为它的全部意义在于产生效果,所以弄乱它会产生明显的后果。
在您的特定情况下,在 toEffect
的正文中,评估是 log $
之后发生的一切。所有对 append
、selectFirstLadderBruteForce
的调用,等等,所有这些都是“评估”。 None 是有效的。您正在执行一些计算以弄清楚您将要创建什么样的效果。
然后,一旦你完成了所有的计算,你将它的结果传递给 log
,这使你成为一个 Effect Unit
,这是“应该发生什么的描述”。在这种特殊情况下,“应该发生什么”非常小——只需向控制台写入一个字符串。
现在,我们终于可以了解 main1
/main2
和 main3
/main4
之间的区别了。
在 main1
和 main2
中,您仅在执行第一个效果后才创建每个下一个效果。所以评估和执行“重叠”,可以这么说:首先你做拳头评估,创造拳头效果,然后你 运行 它,然后,只有在它完成后 运行ning,你才继续做二次评价,创造二次效应。等等。由于昂贵的部分(在您的情况下)是评估,因此每次下一次执行都会延迟下一次评估花费的时间。
另一方面,在 main3
和 main4
中,您首先进行评估,通过在数组上调用 map toEffect
立即创建所有效果,然后才继续一个一个执行。同样,由于评估(在您的情况下)是昂贵的部分,并且所有这些都在开始时发生,因此执行不会延迟。每个效果都非常小(只是打印到控制台),所以它们都执行得非常快。
如果你真的想在上一次执行完成之前阻止下一次评估发生,你可以这样做:在 toEffect
的开头添加一个 pure unit
像这样:
toEffect (Tuple i strng) = do
pure unit
log $ append (show i <> ": ") $
statefulPuzzleToString $
selectFirstLadderBruteForce $
parsePuzzle strng
这将确保在第一行 执行 之前,第二行不会开始 评估 ,从而使每个评估都发生仅在其各自执行之前。
最后,还有一个有趣的事实:在 Haskell 中,相同的程序会以不同的方式运行,因为 Haskell 是惰性的。当被要求进行评估时,它不会立即执行,而只是“记住”它被要求这样做。并且只有当评估的结果确实是必要的时候(这会在执行时发生),才会执行。
另一方面,PureScript 是严格的,这意味着它总是会立即计算所有内容。在这种特殊情况下,这意味着它将计算整个 append
等系列调用,然后才能将其结果传递给 log
.