What I'm trying to do is trivial to define by hand, basically
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'
It's not a big deal to define this locally when needed, but still cumbersone and being so basic and general it seems there should be a standard implementation, yet I can't seem to find one.
Perhaps I'm just overlooking something. What I want seems quite unrelated on the behaviour of the maybe monad, so I reckon I won't find anything in the Monad/Arrow drawers; but it sure resembles the Monoid
instance
Prelude Data.Monoid> Just "a" <> Nothing
Just "a"
Prelude Data.Monoid> Just "a" <> Just "b"
Just "ab"
...
...which however requires a
to be a monoid itself, i.e. that it basically has the a->a->a
"built in". The MonadPlus
instance also behaves much like I want, but it simply throws away one of the values rather than allowing me to supply a combination function
Prelude Data.Monoid Control.Monad> Just 4 `mplus` Nothing
Just 4
Prelude Data.Monoid Control.Monad> Nothing `mplus` Just 4
Just 4
Prelude Data.Monoid Control.Monad> Just 4 `mplus` Just 5
Just 4
What would be the canonical solution? Local pattern matching? Something with combinators from e.g. Data.Maybe
? Defining a custom monoid to do the combining?
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
.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With