{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE FlexibleInstances #-} {-| Module : Data.Has Description : Automatically derivable Has instances. Copyright : (c) Dobromir Nikolov, 2020 License : BSD3 Maintainer : dnikolovv@hotmail.com Stability : experimental Portability : PORTABLE This is a (nearly) drop-in replacement of [data-has](http://hackage.haskell.org/package/data-has). The differences with the original package are that this one misses `hasLens` and uses `Generic` for its default implementation. Your initial reaction may be to start mourning the loss of `hasLens`, but first take a look at the cool things you can do without it! Reduce boilerplate! You can trim down this: @ data Config = Config { configLogEnv :: !LogEnv , configJwtSettings :: !JWTSettings , configMetrics :: !Metrics , configEkgStore :: !EKG.Store } -- Heavy manual instances, data-has only has default implementation for tuples instance Has LogEnv Config where getter = configLogEnv modifier f v = v { configLogEnv = f (configLogEnv v) } instance Has JWTSettings Config where getter = configJwtSettings modifier f v = v { configJwtSettings = f (configJwtSettings v) } instance Has Metrics Config where getter = configMetrics modifier f v = v { configMetrics = f (configMetrics v) } instance Has EKG.Store Config where getter = configEkgStore modifier f v = v { configEkgStore = f (configEkgStore v) } @ To this: @ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} data Config = Config { configLogEnv :: !LogEnv , configJwtSettings :: !JWTSettings , configMetrics :: !Metrics , configEkgStore :: !EKG.Store } deriving (Generic, Has LogEnv, Has JWTSettings, Has Metrics, Has EKG.Store) @ Another trick is that you can "force" a sum type to have a specific field defined. E.g. you may want to define an `Error` type and enforce that it always has an `ErrorText` attached to it. @ newtype ErrorText = ErrorText Text data Error = ValidationError | NotFound | Critical | Unauthorized @ You can do that by deriving `Has ErrorText`. The compiler will error until you have added an `ErrorText` field to each representation. @ data Error = ValidationError ErrorText | NotFound ErrorText | Critical ErrorText | Unauthorized ErrorText deriving (Generic, Has ErrorText) @ For more documentation and examples, please refer to the [original package](http://hackage.haskell.org/package/data-has). -} module Data.Has where import Data.Generics.Internal.VL.Lens (over) import Data.Generics.Product.Typed -- | A type class for an extensible product. class Has a b where getter :: b -> a modifier :: (a -> a) -> b -> b default getter :: HasType a b => b -> a getter = getTyped @a default modifier :: HasType a b => (a -> a) -> b -> b modifier = over $ typed @a instance Has a a where getter = id modifier = id