bbarker
bbarker

Reputation: 13078

Where is the documentation for autogeneration of Has typeclasses in lens?

I was looking at the penultimate example in this blog post (also here), and after verifying it ran, it seemed to confirm that lens can generate Has typeclasses, which I take was the implication from the author of the blog. However, I miss where this is described, either in the lens contents or the lens tutorial. Any explanations external to official docs for how this is done would also be welcome. But it seems like this may just be standard when using the most basic feature (makeLenses, or in this case, makeLensesWith).

Here is the reproduced code:

#!/usr/bin/env stack
-- stack --resolver lts-8.12 script
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
import Control.Concurrent.Async.Lifted.Safe
import Control.Monad.Reader
import Control.Concurrent.STM
import Say
import Control.Lens
import Prelude hiding (log)

data Env = Env
  { envLog :: !(String -> IO ())
  , envBalance :: !(TVar Int)
  }

makeLensesWith camelCaseFields ''Env

modify :: (MonadReader env m, HasBalance env (TVar Int), MonadIO m)
       => (Int -> Int)
       -> m ()
modify f = do
  env <- ask
  liftIO $ atomically $ modifyTVar' (env^.balance) f

logSomething :: (MonadReader env m, HasLog env (String -> IO ()), MonadIO m)
             => String
             -> m ()
logSomething msg = do
  env <- ask
  liftIO $ (env^.log) msg

main :: IO ()
main = do
  ref <- newTVarIO 4
  let env = Env
        { envLog = sayString
        , envBalance = ref
        }
  runReaderT
    (concurrently
      (modify (+ 1))
      (logSomething "Increasing account balance"))
    env
  balance <- readTVarIO ref
  sayString $ "Final balance: " ++ show balance

Upvotes: 3

Views: 118

Answers (1)

bergey
bergey

Reputation: 3071

Field is the word lens uses to describe the pattern of one class per named field, allowing multiple records with the same field name but (optionally) different types. So camelCaseFields, makeFieldOptics, defaultFieldRules all say in their name that they will generate these HasFoo classes, in the usual terse style of lens.

makeClassy also generates classes named Has*, but named after the data type, not the record field, and not following a different pattern.

Your code above generates the following code (shown with -ddump-splices):

    makeLensesWith camelCaseFields ''Env
  ======>
    class HasBalance s a | s -> a where
      balance :: Lens' s a
    instance HasBalance Env (TVar Int) where
      {-# INLINE balance #-}
      balance f_a4eTr (Env x1_a4eTs x2_a4eTt)
        = (fmap (\ y1_a4eTu -> (Env x1_a4eTs) y1_a4eTu)) (f_a4eTr x2_a4eTt)
    class HasLog s a | s -> a where
      log :: Lens' s a
    instance HasLog Env (String -> IO ()) where
      {-# INLINE log #-}
      log f_a4eTx (Env x1_a4eTy x2_a4eTz)
        = (fmap (\ y1_a4eTA -> (Env y1_a4eTA) x2_a4eTz)) (f_a4eTx x1_a4eTy)

Upvotes: 2

Related Questions