【问题标题】:How to use the maybe monoid and combine values with a custom operation, easily?如何轻松地使用可能的幺半群并将值与自定义操作结合起来?
【发布时间】:2013-10-31 12:25:17
【问题描述】:

我想要做的基本上是手动定义是微不足道的

maybeCombine :: (a->a->a) -> Maybe a -> Maybe a -> Maybe a
maybeCombine _ Nothing Nothing = Nothing
maybeCombine _ (Just a) Nothing = Just a
maybeCombine _ Nothing (Just a) = Just a
maybeCombine f (Just a) (Just a') = Just $ f a a'

这对define this locally when needed 来说没什么大不了的,但仍然很麻烦,而且如此基本和通用,似乎应该有一个标准的实现,但我似乎找不到。

也许我只是忽略了一些东西。我想要的似乎与也许 monad 的行为完全无关,所以我想我不会在 Monad/Arrow 抽屉里找到任何东西;但它确实类似于 Monoid 实例

Prelude Data.Monoid> 只是“a” 没有
只是“一个”
Prelude Data.Monoid> 只是“a”只是“b”
只是“ab”
...

...然而,它要求a 本身就是一个幺半群,即它基本上具有a->a->a“内置”。 MonadPlus 实例的行为也很像我想要的,但它只是丢弃了其中一个值,而不是允许我提供组合函数

Prelude Data.Monoid Control.Monad> 仅 4 `mplus` 无
只有 4 个
Prelude Data.Monoid Control.Monad> 没有 `mplus` 只是 4
只有 4 个
Prelude Data.Monoid Control.Monad> 仅 4 `mplus` 仅 5
只有 4 个

规范的解决方案是什么? 本地模式匹配?来自例如组合器的东西Data.Maybe?定义一个自定义幺半群来进行组合?

【问题讨论】:

    标签: haskell monoids


    【解决方案1】:

    您可以随时使用

    f <$> m <*> n <|> m <|> n
    

    但遗憾的是,这在任何地方都没有规范的实现。

    您可以使用reflection(a -&gt; a -&gt; a) “烘焙”为Semigroup 以与Option 一起使用,semigroups 作为Maybe 的改进版本提供'就Semigroup 而言,Monoid 的正确实例。不过,这对于这个问题来说太重了。 =)

    也许这应该只是作为组合器添加到Data.Maybe

    【讨论】:

    • 对,我忘了Alternative!这个挺好的,不过我觉得我更喜欢Semigroup的优雅。
    【解决方案2】:

    当您注意到f 类似于对基础a 类型的Monoid 操作时,您就对了。更具体地说,这里发生的事情是您通过与零 (mempty)、Nothing 相邻来将 Semigroup 提升为 Monoid

    这正是您在 Haddocks 中看到的 Maybe Monoid 实际上。

    根据http://en.wikipedia.org/wiki/Monoid 将一个半群提升为 Maybe 形成一个 Monoid:“任何半群 S 都可以简单地通过邻接不在 S 中的元素 e 并定义 ee = e 和 e变成一个幺半群>s = s = s*e 对于所有 s ∈ S。"由于没有仅提供 mappend 的“Semigroup”类型类,因此我们使用 Monoid。

    或者,如果您喜欢 semigroups 包,那么 Option 就具有这种行为,适当地概括为使用底层 Semigroup 代替。


    这表明最清晰的方法是在基础类型a 上定义MonoidSemigroup 实例。这是一种将组合器 f 与该类型相关联的简洁方法。

    如果您不控制该类型,不想要孤立实例,并且认为 newtype 包装器丑陋怎么办?通常你会很不走运,但这是一个使用完全黑魔法的地方,实际上只有 GHC 的 reflection 包派上用场了。 in the paper itself 有详尽的解释,但 Ausin Seipp's FP Complete Tutorial 包含一些示例代码,允许您将任意半组产品“注入”到没有(尽可能多的)类型定义噪音的类型中......以更可怕的签名为代价。

    然而,这可能比它的价值要多得多。

    【讨论】:

    • 我并不是不走运,在我现在正在处理的问题中,我实际上可以使用Max semigroup 并获得一个非常好的解决方案!
    • 太棒了!我经常最终将Option (Max a) 定义为与类型的“负无穷大”相邻,所以绝对如此。我认为这是一个非常优雅的Monoid
    【解决方案3】:
    import Data.Monoid
    maybeCombine :: (a->a->a) -> Maybe a -> Maybe a -> Maybe a
    maybeCombine f mx my = let combine = mx >>= (\x -> my >>= (\y -> Just (f x y)))
                           in getFirst $ First combine `mappend` First mx `mappend` First` my
    

    在 GHCi 中,这给了我

    *Main> maybeCombine (+) Nothing Nothing
    Nothing
    *Main> maybeCombine (+) (Just 3) Nothing
    Just 3
    *Main> maybeCombine (+) (Just 3) (Just 5)
    Just 8
    

    如果您将Last combine 放在mappend 序列的末尾,也可以与getLast 一起使用

    【讨论】:

    • 我承认这不像 J. Abrahamson 的那样通用 ;)
    • +1 用于回答这个问题以前没有考虑过的(也许是最直接的)方面......但老实说,这个实现既不比我原来愚蠢的模式匹配实现更短,也更易读。我也不太明白它如何更好地适应需求的变化。
    • 您可以将此方法简化为几乎与 Ed 的解决方案一样简洁的东西 — getFirst . mconcat . map First $ [liftA2 f mx my, mx, my],或者甚至将 getFirst . mconcat . map First 替换为 msum,尽管如果没有 First,它可能不太清楚。跨度>
    • @leftaroundabout 加上do 符号会更漂亮,而且我知道它可以通过提升来缩短(谢谢,J ;)但我只是想让基本技术清楚。
    • @leftaroundabout 我也没有在“我也看不出它如何更好地适应需求的变化”中看到任何正义。您当前的需求可以纯粹用 Maybe monad 来表达;如果您更改了需求,但它们仍然可以通过 Maybe 表达,则可以扩展此技术以适应。如果不是,则不是。您想要一个通常适合任何任意变化需求的解决方案吗?好吧,好吧,它叫“Haskell”
    猜你喜欢
    • 2014-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多