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

一笑奈何 提交于 2019-12-03 06:04:34
J. Abrahamson

You're right on the money when you notice that the f is like a Monoid operation on the underlying a type. More specifically what's going on here is you're lifting a Semigroup into a Monoid by adjoining a zero (mempty), Nothing.

This is exactly what you see in the Haddocks for the Maybe Monoid actually.

Lift a semigroup into Maybe forming a Monoid according to http://en.wikipedia.org/wiki/Monoid: "Any semigroup S may be turned into a monoid simply by adjoining an element e not in S and defining ee = e and es = s = s*e for all s ∈ S." Since there is no "Semigroup" typeclass providing just mappend, we use Monoid instead.

Or, if you like the semigroups package, then there's Option which has exactly this behavior, suitably generalized to use an underlying Semigroup instead.


So that suggests the clearest way is to define either a Monoid or Semigroup instance on the underlying type a. It's a clean way to associate some combiner f with that type.

What if you don't control that type, don't want orphan instances, and think a newtype wrapper is ugly? Normally you'd be out of luck, but this is one place where using the total black magic, effectively GHC-only reflection package comes in handy. Thorough explanations exist in the paper itself but Ausin Seipp's FP Complete Tutorial includes some example code to allow you to "inject" arbitrary semigroup products into types without (as much) type definition noise... at the cost of a lot scarier signatures. 

That's probably significantly more overhead than its worth, however.

You can always use

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

But this, sadly, doesn't have a canonical implementation anywhere.

You can use reflection to get that (a -> a -> a) "baked in" as the Semigroup for use with Option, which is provided by semigroups as an improved version of Maybe that has the 'right' instance for Monoid in terms of Semigroup. This is rather too heavy handed for this problem though. =)

Perhaps this should just be added as a combinator to Data.Maybe.

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

in GHCi, this gives me

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

Also works with getLast if you put Last combine at the end of the mappend sequence

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!