{- | If a type derives 'Generic' and all of its fields have 'PartialSemigroup' instances, you can get a 'PartialSemigroup' for free using 'genericPartialSemigroupOp'. == Example For this demonstration we'll define a contrived example type @T@ with two constructors, @A@ and @B@. >>> data T = A String (Either String String) | B String deriving (Generic, Show) And then define its 'PartialSemigroup' instance using 'genericPartialSemigroupOp'. >>> instance PartialSemigroup T where (<>?) = genericPartialSemigroupOp This gives us an implementation of '<>?' which combines values only if they have the same structure. >>> A "s" (Left "x") <>? A "t" (Left "y") Just (A "st" (Left "xy")) >>> B "x" <>? B "y" Just (B "xy") For values that do /not/ have the same structure, '<>?' produces 'Nothing'. >>> A "s" (Left "x") <>? A "t" (Right "y") Nothing >>> A "x" (Left "y") <>? B "z" Nothing -} module Data.PartialSemigroup.Generics ( -- * The generic PartialSemigroup operator genericPartialSemigroupOp -- * Implementation details , PartialSemigroupRep (..) -- * Re-exports , Generic , PartialSemigroup (..) ) where import Data.PartialSemigroup -- base import Control.Applicative ((<$>), (<*>)) import Data.Maybe (Maybe (..)) import GHC.Generics ((:*:) (..), (:+:) (..), Generic, K1 (..), M1 (..), Rep, from, to) {- $setup >>> :set -XDeriveGeneric >>> import Data.Either (Either (..)) >>> import Data.String (String) >>> import Text.Show (Show) -} genericPartialSemigroupOp :: (Generic a, PartialSemigroupRep (Rep a)) => a -> a -> Maybe a genericPartialSemigroupOp x y = to <$> repPartialSemigroupOp (from x) (from y) {- | The class of generic type 'Rep's for which we can automatically derive 'PartialSemigroup': * 'K1' - a single value * 'M1' - a value with some additional metadata (which we simply discard) * ':+:' - sum types * ':*:' - product types -} class PartialSemigroupRep rep where repPartialSemigroupOp :: rep a -> rep a -> Maybe (rep a) instance PartialSemigroup a => PartialSemigroupRep (K1 i a) where repPartialSemigroupOp (K1 x) (K1 y) = K1 <$> (x <>? y) instance PartialSemigroupRep rep => PartialSemigroupRep (M1 i meta rep) where repPartialSemigroupOp (M1 x) (M1 y) = M1 <$> repPartialSemigroupOp x y instance (PartialSemigroupRep rep1, PartialSemigroupRep rep2) => PartialSemigroupRep (rep1 :*: rep2) where repPartialSemigroupOp (x1 :*: x2) (y1 :*: y2) = (:*:) <$> repPartialSemigroupOp x1 y1 <*> repPartialSemigroupOp x2 y2 instance (PartialSemigroupRep rep1, PartialSemigroupRep rep2) => PartialSemigroupRep (rep1 :+: rep2) where repPartialSemigroupOp (L1 x) (L1 y) = L1 <$> repPartialSemigroupOp x y repPartialSemigroupOp (R1 x) (R1 y) = R1 <$> repPartialSemigroupOp x y repPartialSemigroupOp _ _ = Nothing