{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances #-} module Data.Semigroup.Generic ( genericMappend , GenericSemigroup(..) ) where import GHC.TypeLits import Data.Semigroup import GHC.Generics -- | A newtype which allows you to using the @DerivingVia@ extension -- to reduce boilerplate. -- -- @ -- data X = X [Int] String -- deriving (Generic, Show) -- deriving Semigroup via GenericSemigroup X -- @ newtype GenericSemigroup a = GenericSemigroup a instance (Generic a, MappendProduct (Rep a)) => Semigroup (GenericSemigroup a) where (GenericSemigroup a) <> (GenericSemigroup b) = GenericSemigroup $ genericMappend a b -- | A generic @`<>`@ function which works for product types where each -- contained type is itself a @`Semigroup`@. It simply calls @`<>`@ for -- each field. -- -- If you don't want to use the @deriving via@ mechanism, use this function -- to implement the `Semigroup` type class. genericMappend :: (Generic a, MappendProduct (Rep a)) => a -> a -> a genericMappend a b = to $ from a `genericMappend'` from b class MappendProduct f where genericMappend' :: f k -> f k -> f k instance (TypeError (Text "You can't use `genericMappend` for sum types")) => MappendProduct (a :+: b) where genericMappend' = undefined instance MappendProduct c => MappendProduct (D1 md c) where genericMappend' (M1 a) (M1 b) = M1 (genericMappend' a b) instance MappendProduct s => MappendProduct (C1 mc s) where genericMappend' (M1 a) (M1 b) = M1 (genericMappend' a b) instance (MappendProduct a, MappendProduct b) => MappendProduct (a :*: b) where genericMappend' (a :*: b) (a' :*: b') = genericMappend' a a' :*: genericMappend' b b' instance Semigroup t => MappendProduct (S1 m (Rec0 t)) where genericMappend' (M1 (K1 a)) (M1 (K1 b)) = M1 (K1 (a <> b))