Generic usage of Has Field env with Records

Viewed 116

Is there any way to use the Has Field env type class with Records.

The data-has package provides instances for Tuples but I can't seem to figure any way to use it with records without manually providing instances.

I've been looking at the way kowainik approached it in cake-slayer and it seems nice but still requires the manual instances to be generated.

module CakeSlayer.Has
       ( Has (..)
       , grab

         -- * Deriving helpers
       , Field (..)
       ) where

import GHC.Records (HasField (getField))
import GHC.TypeLits (Symbol)

class Has field env where
    obtain :: env -> field

-- | General function to retrieve fields with 'Has' class.
grab :: forall field env m . (MonadReader env m, Has field env) => m field
grab = asks $ obtain @field
{-# INLINE grab #-}

newtype Field (s :: Symbol) env = Field
    { unField :: env
    }

instance forall s f env . (HasField s env f) => Has f (Field s env) where
    obtain :: Field s env -> f
    obtain = getField @s . unField
    {-# INLINE obtain #-}

is there any way where i can do something like the following:

data Env = Env
 { pgEnv :: PG
 , rabbitEnv :: Rabbit
 ... many other fields
 } deriving (Has)

runPgStuff :: (MonadReader env m, Has PG env) => m ()
runPgStuff = do
  e <- asks pgEnv -- | get pg env
  run e query

This would help specifically for testing purposes where i might have a monolithic Env type with 20 fields but the scope of my function under test is only using 2 of those fields. In that case i can do something like:

import MyModule (runPgStuff)

data EnvMock = EnvMock
  { pgEnv :: PG
  } deriving (Has)

runReaderT runPgStuff envMock

Maybe i'm going about this in a bad way so any advice on a better approach is appreciated. Another thought i had was defining the manual instances for my monolithic Env type and then using the provided tuple instances in data-has for testing.

0 Answers
Related