This is similar to Alternative . Maybe Alternative instance implements a left-biased choice - its <|> is selected by the first value not Nothing .
import Control.Applicative import Data.Semigroup data Foo = Foo { bar :: Maybe Int, baz :: Maybe String }
I am going to implement a Semigroup instance for Foo , which takes <|> points by field of the record. Thus, the operation x <> y overrides the y fields with the corresponding fields << 24> x . (You can also use the monoid First , it does the same.)
instance Semigroup Foo where f1 <> f2 = Foo { bar = bar f1 <|> bar f2, baz = baz f1 <|> baz f2 } ghci> let defaultFoo = Foo { bar = Just 2, baz = Just "default" } ghci> let overrides = Foo { bar = Just 8, baz = Nothing } ghci> overrides <> defaultFoo Foo {bar = Just 8, baz = Just "default"}
Note that you do not need lenses for this, although they can help you make a small copy (<>) .
When the user gives you a partially filled Foo , you can fill in the rest of the fields by adding Foo by default.
fillInDefaults :: Foo -> Foo fillInDefaults = (<> defaultFoo)
One fun thing you can do with this is the Maybe definition from Foo .
{-# LANGUAGE RankNTypes #-} import Control.Applicative import Data.Semigroup import Data.Functor.Identity data Foo f = Foo { bar :: f Int, baz :: f String }
Foo , which I originally wrote above, is now equivalent to Foo Maybe . But now you can express invariants such as โthis Foo has all the fields filled inโ without duplicating Foo itself.
type PartialFoo = Foo Maybe
The Semigroup instance, which referred only to the Maybe instance of Alternative , remains unchanged,
instance Alternative f => Semigroup (Foo f) where f1 <> f2 = Foo { bar = bar f1 <|> bar f2, baz = baz f1 <|> baz f2 }
but now you can generalize defaultFoo to an arbitrary Applicative .
defaultFoo :: Applicative f => Foo f defaultFoo = Foo { bar = pure 2, baz = pure "default" }
Now, having a bit of Traversable inspired categorical nonsense,
-- "higher order functors": functors from the category of endofunctors to the category of types class HFunctor t where hmap :: (forall x. fx -> gx) -> tf -> tg -- "higher order traversables", -- about which I have written a follow up question: https://stackoverflow.com/q/44187945/7951906 class HFunctor t => HTraversable t where htraverse :: Applicative g => (forall x. fx -> gx) -> tf -> g (t Identity) htraverse eta = hsequence . hmap eta hsequence :: Applicative f => tf -> f (t Identity) hsequence = htraverse id instance HFunctor Foo where hmap eta (Foo bar baz) = Foo (eta bar) (eta baz) instance HTraversable Foo where htraverse eta (Foo bar baz) = liftA2 Foo (Identity <$> eta bar) (Identity <$> eta baz)
fillInDefaults can be customized to express the invariant that there are no values โโas a result of Foo .
fillInDefaults :: Alternative f => Foo f -> f TotalFoo fillInDefaults = hsequence . (<> defaultFoo) -- fromJust (unsafely) asserts that there aren't -- any `Nothing`s in the output of `fillInDefaults` fillInDefaults' :: PartialFoo -> TotalFoo fillInDefaults' = fromJust . fillInDefaults
Probably redundant than you need, but it's still pretty neat.