【问题标题】:Fold a Foldable of Maybe (Monoid) ignoring the missing values in Haskell折叠可能(Monoid)的可折叠,忽略 Haskell 中的缺失值
【发布时间】:2016-10-06 09:37:57
【问题描述】:

我正在处理一些缺少值的数据,这些数据只是简单地表示为 Maybe 值的列表。我想执行各种聚合/统计操作,这些操作只是忽略缺失值。

这与以下问题有关:

Idiomatic way to sum a list of Maybe Int in haskell

How to use the maybe monoid and combine values with a custom operation, easily?

但是,如果缺少任何值,前一个问题满足于返回 Nothing,这在我的情况下不是一个选项。我有一个解决方案,它涉及为Maybe 创建一个Num 实例。然而,这意味着它是特定于加法和乘法的,它也有一些其他的问题。

instance Num a => Num (Maybe a) where
  negate      = fmap negate
  (+)         = liftA2 (+)
  (*)         = liftA2 (*)
  fromInteger = pure . fromInteger
  abs         = fmap abs
  signum      = fmap signum

基于此,我们可以这样做:

maybeCombineW :: (a -> a -> a) -> Maybe a -> Maybe a -> Maybe a
maybeCombineW f (Just x)  (Just y)  = Just (f x y)
maybeCombineW _ (Just x)  Nothing   = Just x
maybeCombineW _ Nothing   (Just y)  = Just y
maybeCombineW _ Nothing   Nothing   = Nothing


maybeCombineS :: (a -> a -> a) -> Maybe a -> Maybe a -> Maybe a
maybeCombineS f (Just x)  (Just y)  = Just (f x y)
maybeCombineS _ _          _        = Nothing


class (Num a) => Num' a where
  (+?) :: a -> a -> a
  (*?) :: a -> a -> a
  (+!) :: a -> a -> a
  (*!) :: a -> a -> a
  (+?) = (+)
  (*?) = (*)
  (+!) = (+)
  (*!) = (*)

instance {-# OVERLAPPABLE  #-} (Num a) => Num' a
instance {-# OVERLAPPING  #-} (Num' a) => Num' (Maybe a) where
  (+?) = maybeCombineW (+?)
  (*?) = maybeCombineW (*?)
  (+!) = maybeCombineS (+!)
  (*!) = maybeCombineS (*!)


sum' :: (Num' b, Foldable t) => t b -> b
sum' = foldr (+?) 0

sum'' :: (Num' b, Foldable t) => t b -> b
sum'' = foldr (+!) 0

我喜欢这个:它给了我两个功能,一个宽松的sum' 和一个严格的sum'',我可以根据需要从中选择。我可以使用相同的函数对任何 Num 实例求和,因此我可以为没有 Maybe 的列表重用相同的代码,而无需先转换它们。

我不喜欢这个:实例重叠。另外,对于除加法和乘法之外的任何操作,我都必须指定一个新的类型类并创建新的实例。

因此,我想知道是否有可能获得一个不错的通用解决方案,也许与第二个问题中建议的思路一致,它将Nothing 视为mempty 用于任何有问题的操作。

有没有很好的惯用方法?

编辑:这是迄今为止最好的解决方案:

inout i o = ((fmap o) . getOption) . foldMap (Option . (fmap i))
sum' = Sum `inout` getSum
min' = Min `inout` getMin
-- etc.

【问题讨论】:

    标签: haskell fold maybe monoids


    【解决方案1】:

    Monoid 的一个实例完全正确:

    instance Monoid a => Monoid (Maybe a) where
      mempty = Nothing
      Nothing `mappend` m = m
      m `mappend` Nothing = m
      Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)
    

    它在Data.Monoid

    因此,

    foldMap (liftA Sum) [Just 1, Nothing, Just 2, Nothing, Just 3] = 
       fold [Just (Sum 1), Nothing, Just (Sum 2), Nothing, Just (Sum 3)] = 
          Just (Sum 6)
    

    对于严格的左折叠版本,可以使用foldl' mappend mempty 代替fold,使用foldl' (mappend . f) mempty 代替foldMap f。在Maybe 半群中,memptyNothing

    【讨论】:

    • 很酷,感谢您指出这一点,这绝对看起来更惯用。所以我可以写sum' = (liftA getSum) . foldMap (liftA Sum)。但是,这意味着如果我确实想重用一些使用 sum' 且没有缺失值的代码,我仍然必须先将值包装在 Maybe 中,对吧? (并不是说如果那样的话会很糟糕,只是检查......)
    • 另外,我将如何将这种方法与另一个操作一起使用,例如获取列表的最小值/最大值? (并且还假设我想要一个宽松版本和一个严格版本,就像在我的例子中一样。如果你能展示如何做到这一点,我很乐意接受答案!)
    • 不确定是否可以将相同的函数应用于[a][Maybe a] 并获得相同类型的结果。如果aMaybe b 怎么办?
    • 例如,您可以使用liftA ProductMin 不是幺半群(空列表的最小值是多少?),但您可以编写自己的类似 Min 的包装器,以便 Maybe (MyMin a) 是一个幺半群,并使用 liftA MyMin 进行可折叠的可能。
    • 是的,关于类型歧义的公平点,我想这是我的解决方案中重叠实例的根源。你对Min 不是Monoid 也是正确的,所以我可能不应该把另一件事放在标题中。关键是它不必是 Monoid,因为在空列表上结果可以是 Nothing。您的回答帮助我更好地理解了这个问题,我现在已经使用Data.Semigroup 实现了一个非常好的解决方案(见编辑)。除非出现任何其他聪明的解决方案,否则我会接受。
    【解决方案2】:

    仅使用来自Data.MaybecatMaybes 来丢弃所有Nothing 值怎么样?然后,您可以在一个纯值列表上运行任何聚合和计算。

    【讨论】:

    • 是的,我知道catMaybes,但感觉还不是问题的根源。例如,假设我想做一个累积总和,而不是一个简单的总和,并继续为 Nothing 元素添加零? (虽然,在那种情况下,我想我不应该说在牌中弃牌,这是我的错误。)
    猜你喜欢
    • 1970-01-01
    • 2017-02-18
    • 1970-01-01
    • 2012-12-01
    • 1970-01-01
    • 2015-02-09
    • 2018-12-13
    • 1970-01-01
    • 2015-08-30
    相关资源
    最近更新 更多