在单子列表上累积一个函数(也许)

Accumulate a Function Over a List of Monads (Maybe)

提问人:Philip Adler 提问时间:3/13/2023 最后编辑:cafce25Philip Adler 更新时间:3/14/2023 访问量:100

问:

我正在学习 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 来达到这个目的。

列表 哈斯克尔 选项类型 单子

评论

1赞 leftaroundabout 3/13/2023
与您的问题无关的注释:对于原始(特别是完全严格)操作,例如 ,您通常应该使用 (not !) 而不是 。或者只使用 / .+*foldl'foldlfoldrsumproduct

答:

2赞 bereal 3/13/2023 #1

您可以使用 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所建议的那样,更通用(然后替换为上面)liftA2returnpure

3赞 leftaroundabout 3/13/2023 #2

你不需要任何神奇的折叠,你只需要一个合适的操作员来折叠。请注意,您的问题实际上与列表无关,您也可以要求

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 -> IntMaybe Int -> Maybe Int -> Maybe IntInt

    (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c

让我们问问 Hoogle,它会回来的

liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c

...使用 MaybeApplicative 类的实例这一事实。

(当然,拥有正确类型的事实并不能证明它确实做了正确的事情;在使用 Hoogle 建议的东西之前,您应该始终阅读文档并运行一些简单的测试!但是,在这种情况下,Hoogle 上的第一次点击确实是您应该使用的。liftA2

4赞 chepner 3/13/2023 #3

这里的问题是需要 type 的值(我们假设 ,而不是为了简单起见),而您有一个 type 的值。您可以通过将值和预期的参数类型更改为通用类型来弥合此差距,如下所示。foldr (*) 1[Int]IntNum a => a[Maybe Int]Maybe [Int]

  1. 用于将值折叠为单个值。sequence[Maybe Int]Maybe [Int]

    > sequence [Just 1, Just 2, Just 5, Nothing]
    Nothing
    > sequence [Just 2, Just 3]
    Just [2,3]
    
  2. 用于从 “提升”到 。fmapfoldr (*) 1[Int] -> IntMaybe [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

这与其他答案中的 和 的使用类似,只是我们抬起整个折叠而不是使用抬起的操作员折叠。liftA2liftM2

“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

(使用 对实例施加约束,以及将实例上的约束“升级”为约束。sequenceAApplicativeMaybeFoldable[]Traversable