提问人:Philip Adler 提问时间:3/13/2023 最后编辑:cafce25Philip Adler 更新时间:3/14/2023 访问量:100
在单子列表上累积一个函数(也许)
Accumulate a Function Over a List of Monads (Maybe)
问:
我正在学习 Haskell,以此来掌握一种新的编程范式。我挂在一个特定的,可能是基本的问题上。虽然我发现了很多文档和其他堆栈溢出问题,但我觉得在试图弄清楚如何让它工作时,我迷失在其他人示例的噪音中。
至关重要的是,我不仅(啊哈)希望让它发挥作用,我还希望理解抽象,到目前为止我绝对没有做到。
假设我们有一个 Ints 列表
x = [1, 2, 5]
为了获得这些 Ints 的产品,我知道我可以做到:
result = foldr (*) 1 x
如果这个列表是 Maybe Ints,我正在尝试弄清楚如何做类似的事情
x = [Just 1, Just 2, Just 5, Nothing]
res_from_x = <magic_fold> (*) 1 x
-- res from x is Nothing
y = [Just 2, Just 3]
res_from_y = <magic_fold> (*) 1 y
-- res from y is 6
我敢肯定这有一个简单而惯用的抽象,但我还没有设法弄清楚如何操作 fold 或使用 foldM 来达到这个目的。
答:
您可以使用 liftM2
,例如:
foldr (liftM2 (*)) (Just 1) [Just 2, Just 3] -- Just 6
foldr (liftM2 (*)) (Just 1) [Just 2, Just 3, Nothing] -- Nothing
或者,要将其提取到新函数中:
magicFoldr f i = foldr (liftM2 f) (return i)
magicFoldr (*) 1 [Just 2, Just 3] -- Just 6
magicFoldr (*) 1 [Just 2, Just 3, Nothing] -- Nothing
更新:或者,正如@leftaroundabout所建议的那样,更通用(然后替换为上面)liftA2
return
pure
你不需要任何神奇的折叠,你只需要一个合适的操作员来折叠。请注意,您的问题实际上与列表无关,您也可以要求
x₀ = Just 5
x₁ = Nothing
res_from_x = <magic_combo> (*) x₀ x₁
-- result is `Nothing`
y₀ = Just 2
y₁ = Just 3
res_from_y = <magic_combo> (*) y₀ y₁
-- result is `Just 6`
所以你需要一些组合器来接受一个操作并将其提升为一个操作。进一步注意,此组合器实际上不应要求操作数具有类型或任何其他特定类型,因为只有操作应该处理实际值。所以我们要找的类型似乎是Int -> Int -> Int
Maybe Int -> Maybe Int -> Maybe Int
Int
(a -> b -> c) -> Maybe a -> Maybe b -> Maybe c
让我们问问 Hoogle,它会回来的
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
...使用 Maybe
是 Applicative
类的实例这一事实。
(当然,拥有正确类型的事实并不能证明它确实做了正确的事情;在使用 Hoogle 建议的东西之前,您应该始终阅读文档并运行一些简单的测试!但是,在这种情况下,Hoogle 上的第一次点击确实是您应该使用的。liftA2
这里的问题是需要 type 的值(我们假设 ,而不是为了简单起见),而您有一个 type 的值。您可以通过将值和预期的参数类型更改为通用类型来弥合此差距,如下所示。foldr (*) 1
[Int]
Int
Num a => a
[Maybe Int]
Maybe [Int]
用于将值折叠为单个值。
sequence
[Maybe Int]
Maybe [Int]
> sequence [Just 1, Just 2, Just 5, Nothing] Nothing > sequence [Just 2, Just 3] Just [2,3]
用于从 “提升”到 。
fmap
foldr (*) 1
[Int] -> Int
Maybe [Int] -> Maybe Int
最终结果是
> fmap (foldr (*) 1) (sequence [Just 1, Just 2, Just 5, Nothing])
Nothing
> fmap (foldr (*) 1) (sequence [Just 2, Just 3])
Just 6
这与其他答案中的 和 的使用类似,只是我们抬起整个折叠而不是使用抬起的操作员折叠。liftA2
liftM2
“magic”函数(我们将给出其完全泛化的类型)是
import Control.Applicative
magicFoldr :: (Traversable t, Applicative f) => (a -> b -> b) -> b -> t (f a) -> f b
magicFoldr f z = fmap (foldr f z) . sequenceA
(使用 对实例施加约束,以及将实例上的约束“升级”为约束。sequenceA
Applicative
Maybe
Foldable
[]
Traversable
评论
+
*
foldl'
foldl
foldr
sum
product