it-has-0.1.0.0: src/Data/Has.hs
{-# 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