optics-th-0.4.1: src/Optics/TH.hs
module Optics.TH
(
-- * Generation of field optics
-- ** Labels
makeFieldLabels
, makeFieldLabelsNoPrefix
, makeFieldLabelsFor
, makeFieldLabelsWith
, declareFieldLabels
, declareFieldLabelsFor
, declareFieldLabelsWith
, fieldLabelsRules
, fieldLabelsRulesFor
-- ** Functions
, makeLenses
, makeLensesFor
, makeLensesWith
, declareLenses
, declareLensesFor
, declareLensesWith
, lensRules
, lensRulesFor
-- ** Single class per data type
-- $deprecatedClassy
, makeClassy
, makeClassy_
, makeClassyFor
, declareClassy
, declareClassyFor
, classyRules
, classyRules_
, classyRulesFor
-- ** Multiple classes per data type
-- $deprecatedFields
, makeFields
, makeFieldsNoPrefix
, declareFields
, defaultFieldRules
-- * Generation of constructor optics
-- ** Labels
, makePrismLabels
-- ** Functions
, makePrisms
, declarePrisms
-- ** Single class per data type
, makeClassyPrisms
-- * Generation rules for field optics
, LensRules
, simpleLenses
, generateSignatures
, generateUpdateableOptics
, generateLazyPatterns
, createClass
, lensField
, lensClass
-- * Common rules
, noPrefixFieldLabels
, abbreviatedFieldLabels
, underscoreFields
, camelCaseFields
, classUnderscoreNoPrefixFields
, abbreviatedFields
-- * Field namers
, FieldNamer
, ClassyNamer
, DefName(..)
, noPrefixNamer
, underscoreNoPrefixNamer
, lookingupNamer
, mappingNamer
, underscoreNamer
, camelCaseNamer
, classUnderscoreNoPrefixNamer
, abbreviatedNamer
) where
import Control.Monad.Trans.Class
import Control.Monad.Trans.State
import Control.Monad.Trans.Writer
import Data.Char (toLower, toUpper, isUpper)
import Data.Maybe (maybeToList)
import Data.Monoid
import Data.Set (Set)
import Language.Haskell.TH.Syntax hiding (lift)
import Language.Haskell.TH
import qualified Data.List as L
import qualified Data.Set as Set
import Optics.Core hiding (cons)
import Optics.TH.Internal.Product
import Optics.TH.Internal.Sum
----------------------------------------
-- Labels
-- | Build field optics as instances of the 'LabelOptic' class for use with
-- overloaded labels. See "Optics.Label" for how to use this pattern.
--
-- /e.g./
--
-- @
-- data Animal
-- = Cat { animalAge :: Int
-- , animalName :: String
-- }
-- | Dog { animalAge :: Int
-- , animalAbsurd :: forall a b. a -> b
-- }
-- makeFieldLabels ''Animal
-- @
--
-- will create
--
-- @
-- instance
-- (k ~ A_Lens, a ~ Int, b ~ Int
-- ) => LabelOptic "age" k Animal Animal a b where
-- labelOptic = lensVL $ \\f s -> case s of
-- Cat x1 x2 -> fmap (\\y -> Cat y x2) (f x1)
-- Dog x1 x2 -> fmap (\\y -> Dog y x2) (f x1)
--
-- instance
-- (k ~ An_AffineTraversal, a ~ String, b ~ String
-- ) => LabelOptic "name" k Animal Animal a b where
-- labelOptic = atraversalVL $ \\point f s -> case s of
-- Cat x1 x2 -> fmap (\\y -> Cat x1 y) (f x2)
-- Dog x1 x2 -> point (Dog x1 x2)
--
-- instance
-- ( Dysfunctional "absurd" k Animal Animal a b
-- , k ~ An_AffineFold, a ~ (x -> y), b ~ (x -> y)
-- ) => LabelOptic "absurd" k Animal Animal a b where
-- labelOptic = afolding $ \\s -> case s of
-- Cat _ _ -> Nothing
-- Dog _ f -> Just f
-- @
--
-- which can be used as @#age@, @#name@ and @#absurd@ with the
-- @OverloadedLabels@ language extension.
--
-- /Note:/ if you wonder about the structure of instances, see
-- "Optics.Label#structure".
--
-- @
-- 'makeFieldOptics' = 'makeFieldLabelsWith' 'fieldLabelsRules'
-- @
makeFieldLabels :: Name -> DecsQ
makeFieldLabels = makeFieldLabelsWith fieldLabelsRules
-- | An alias for @makeFieldLabels noPrefixFieldLabels@.
makeFieldLabelsNoPrefix :: Name -> DecsQ
makeFieldLabelsNoPrefix = makeFieldLabelsWith noPrefixFieldLabels
-- | Derive field optics as labels, specifying explicit pairings of @(fieldName,
-- labelName)@.
--
-- If you map multiple fields to the same label and it is present in the same
-- constructor, 'Traversal' (or 'Fold' for a read only version) will be
-- generated.
--
-- /e.g./
--
-- @
-- 'makeFieldLabelsFor' [(\"_foo\", \"fooLens\"), (\"baz\", \"lbaz\")] ''Foo
-- 'makeFieldLabelsFor' [(\"_barX\", \"bar\"), (\"_barY\", \"bar\")] ''Bar
-- @
makeFieldLabelsFor :: [(String, String)] -> Name -> DecsQ
makeFieldLabelsFor fields = makeFieldLabelsWith (fieldLabelsRulesFor fields)
-- | Make field optics as labels for all records in the given declaration
-- quote. All record syntax in the input will be stripped off.
--
-- /e.g./
--
-- @
-- declareLenses [d|
-- data Dog = Dog { name :: String, age :: Int }
-- deriving Show
-- |]
-- @
--
-- will create
--
-- @
-- data Dog = Dog String Int
-- deriving Show
-- instance (k ~ A_Lens, ...) => LabelOptic "name" k Dog Dog ...
-- instance (k ~ A_Lens, ...) => LabelOptic "age" k Dog Dog ...
-- @
declareFieldLabels :: DecsQ -> DecsQ
declareFieldLabels
= declareFieldLabelsWith
$ fieldLabelsRules
& lensField .~ noPrefixNamer
-- | Similar to 'makeFieldLabelsFor', but takes a declaration quote.
declareFieldLabelsFor :: [(String, String)] -> DecsQ -> DecsQ
declareFieldLabelsFor fields
= declareFieldLabelsWith
$ fieldLabelsRulesFor fields
& lensField .~ noPrefixNamer
-- Similar to 'makeFieldLabelsWith', but takes a declaration quote.
declareFieldLabelsWith :: LensRules -> DecsQ -> DecsQ
declareFieldLabelsWith rules = declareWith $ \dec -> do
emit =<< liftDeclare (makeFieldLabelsForDec rules dec)
return $ stripFields dec
-- | Rules for generation of 'LabelOptic' instances for use with
-- OverloadedLabels. Same as 'lensRules', but uses 'camelCaseNamer'.
--
-- /Note:/ if you don't want to prefix field names with the full name of the
-- data type, you can use 'abbreviatedNamer' instead.
fieldLabelsRules :: LensRules
fieldLabelsRules = LensRules
{ _simpleLenses = False
, _generateSigs = True
, _generateClasses = False
, _allowIsos = True
, _allowUpdates = True
, _lazyPatterns = False
, _classyLenses = const Nothing
, _fieldToDef = camelCaseNamer
}
-- | Construct a 'LensRules' value for generating 'LabelOptic' instances using
-- the given map from field names to definition names.
fieldLabelsRulesFor
:: [(String, String)] {- ^ [(Field name, Label name)] -}
-> LensRules
fieldLabelsRulesFor fields = fieldLabelsRules & lensField .~ lookingupNamer fields
----------------------------------------
-- Lenses
-- | Build field optics as top level functions with a sensible default
-- configuration.
--
-- /e.g./
--
-- @
-- data Animal
-- = Cat { _age :: 'Int'
-- , _name :: 'String'
-- }
-- | Dog { _age :: 'Int'
-- , _absurd :: forall a b. a -> b
-- }
-- 'makeLenses' ''Animal
-- @
--
-- will create
--
-- @
-- absurd :: forall a b. AffineFold Animal (a -> b)
-- absurd = afolding $ \\s -> case s of
-- Cat _ _ -> Nothing
-- Dog _ x -> Just x
--
-- age :: Lens' Animal Int
-- age = lensVL $ \\f s -> case s of
-- Cat x1 x2 -> fmap (\\y -> Cat y x2) (f x1)
-- Dog x1 x2 -> fmap (\\y -> Dog y x2) (f x1)
--
-- name :: AffineTraversal' Animal String
-- name = atraversalVL $ \\point f s -> case s of
-- Cat x1 x2 -> fmap (\\y -> Cat x1 y) (f x2)
-- Dog x1 x2 -> point (Dog x1 x2)
-- @
--
-- @
-- 'makeLenses' = 'makeLensesWith' 'lensRules'
-- @
makeLenses :: Name -> DecsQ
makeLenses = makeFieldOptics lensRules
-- | Derive field optics, specifying explicit pairings of @(fieldName,
-- opticName)@.
--
-- If you map multiple fields to the same optic and it is present in the same
-- constructor, 'Traversal' (or 'Fold' for a read only version) will be
-- generated.
--
-- /e.g./
--
-- @
-- 'makeLensesFor' [(\"_foo\", \"fooLens\"), (\"baz\", \"lbaz\")] ''Foo
-- 'makeLensesFor' [(\"_barX\", \"bar\"), (\"_barY\", \"bar\")] ''Bar
-- @
makeLensesFor :: [(String, String)] -> Name -> DecsQ
makeLensesFor fields = makeFieldOptics (lensRulesFor fields)
-- | Build field optics with a custom configuration.
makeLensesWith :: LensRules -> Name -> DecsQ
makeLensesWith = makeFieldOptics
-- | Make field optics for all records in the given declaration quote. All
-- record syntax in the input will be stripped off.
--
-- /e.g./
--
-- @
-- declareLenses [d|
-- data Foo = Foo { fooX, fooY :: 'Int' }
-- deriving 'Show'
-- |]
-- @
--
-- will create
--
-- @
-- data Foo = Foo 'Int' 'Int' deriving 'Show'
-- fooX, fooY :: 'Lens'' Foo Int
-- @
declareLenses :: DecsQ -> DecsQ
declareLenses
= declareLensesWith
$ lensRules
& lensField .~ noPrefixNamer
-- | Similar to 'makeLensesFor', but takes a declaration quote.
declareLensesFor :: [(String, String)] -> DecsQ -> DecsQ
declareLensesFor fields
= declareLensesWith
$ lensRulesFor fields
& lensField .~ noPrefixNamer
-- | 'declareLenses' with custom 'LensRules'.
declareLensesWith :: LensRules -> DecsQ -> DecsQ
declareLensesWith rules = declareWith $ \dec -> do
emit =<< lift (makeFieldOpticsForDec' rules dec)
return $ stripFields dec
-- | Rules for making read-write field optics as top-level functions. It uses
-- 'underscoreNoPrefixNamer'.
lensRules :: LensRules
lensRules = LensRules
{ _simpleLenses = False
, _generateSigs = True
, _generateClasses = False
, _allowIsos = True
, _allowUpdates = True
, _lazyPatterns = False
, _classyLenses = const Nothing
, _fieldToDef = underscoreNoPrefixNamer
}
-- | Construct a 'LensRules' value for generating top-level functions using the
-- given map from field names to definition names.
lensRulesFor
:: [(String, String)] {- ^ [(Field name, Optic name)] -}
-> LensRules
lensRulesFor fields = lensRules & lensField .~ lookingupNamer fields
----------------------------------------
-- Classy
-- $deprecatedClassy
--
-- This method of optics generation should only be used when migrating an
-- existing codebase from the @lens@ library to @optics@ as it:
--
-- - Doesn't support prefixless fields.
--
-- - Doesn't support type changing updates.
--
-- See "Optics.Label" for our recommended pattern.
-- | Make lenses and traversals for a type, and create a class when the type has
-- no arguments.
--
-- /e.g./
--
-- @
-- data Foo = Foo { _fooX, _fooY :: 'Int' }
-- 'makeClassy' ''Foo
-- @
--
-- will create
--
-- @
-- class HasFoo c where
-- foo :: Lens' c Foo
-- fooX :: Lens' c Int
-- fooY :: Lens' c Int
-- fooX = foo % fooX
-- fooY = foo % fooY
--
-- instance HasFoo Foo where
-- foo = lensVL id
-- fooX = lensVL $ \\f s -> case s of
-- Foo x1 x2 -> fmap (\\y -> Foo y x2) (f x1)
-- fooY = lensVL $ \\f s -> case s of
-- Foo x1 x2 -> fmap (\\y -> Foo x1 y) (f x2)
-- @
--
-- @
-- 'makeClassy' = 'makeLensesWith' 'classyRules'
-- @
makeClassy :: Name -> DecsQ
makeClassy = makeFieldOptics classyRules
-- | Make lenses and traversals for a type, and create a class when the type has
-- no arguments. Works the same as 'makeClassy' except that (a) it expects that
-- record field names do not begin with an underscore, (b) all record fields are
-- made into lenses, and (c) the resulting lens is prefixed with an underscore.
makeClassy_ :: Name -> DecsQ
makeClassy_ = makeFieldOptics classyRules_
-- | Derive lenses and traversals, using a named wrapper class, and
-- specifying explicit pairings of @(fieldName, traversalName)@.
--
-- Example usage:
--
-- @
-- 'makeClassyFor' \"HasFoo\" \"foo\" [(\"_foo\", \"fooLens\"), (\"bar\", \"lbar\")] ''Foo
-- @
makeClassyFor :: String -> String -> [(String, String)] -> Name -> DecsQ
makeClassyFor clsName funName fields = makeFieldOptics $
classyRulesFor (const (Just (clsName, funName))) fields
-- | For each record in the declaration quote, make lenses and traversals for
-- it, and create a class when the type has no arguments. All record syntax
-- in the input will be stripped off.
--
-- /e.g./
--
-- @
-- declareClassy [d|
-- data Foo = Foo { fooX, fooY :: 'Int' }
-- deriving 'Show'
-- |]
-- @
--
-- will create
--
-- @
-- data Foo = Foo 'Int' 'Int' deriving 'Show'
-- class HasFoo t where
-- foo :: 'Lens'' t Foo
-- instance HasFoo Foo where foo = 'id'
-- fooX, fooY :: HasFoo t => 'Lens'' t 'Int'
-- @
declareClassy :: DecsQ -> DecsQ
declareClassy
= declareLensesWith
$ classyRules
& lensField .~ noPrefixNamer
-- | Similar to 'makeClassyFor', but takes a declaration quote.
declareClassyFor ::
[(String, (String, String))] -> [(String, String)] -> DecsQ -> DecsQ
declareClassyFor classes fields
= declareLensesWith
$ classyRulesFor (`Prelude.lookup`classes) fields
& lensField .~ noPrefixNamer
-- | Rules for making lenses and traversals that precompose another 'Lens'.
classyRules :: LensRules
classyRules = LensRules
{ _simpleLenses = True
, _generateSigs = True
, _generateClasses = True
, _allowIsos = False -- generating Isos would hinder "subtyping"
, _allowUpdates = True
, _lazyPatterns = False
, _classyLenses = \n ->
case nameBase n of
x:xs -> Just (mkName ("Has" ++ x:xs), mkName (toLower x:xs))
[] -> Nothing
, _fieldToDef = underscoreNoPrefixNamer
}
-- | A 'LensRules' used by 'makeClassy_'.
classyRules_ :: LensRules
classyRules_
= classyRules & lensField .~ \_ _ n -> [TopName (mkName ('_':nameBase n))]
-- | Rules for making lenses and traversals that precompose another 'Lens' using
-- a custom function for naming the class, main class method, and a mapping from
-- field names to definition names.
classyRulesFor
:: (String -> Maybe (String, String)) {- ^ Type Name -> Maybe (Class Name, Method Name) -} ->
[(String, String)] {- ^ [(Field Name, Method Name)] -} ->
LensRules
classyRulesFor classFun fields = classyRules
& lensClass .~ (over (mapped % each) mkName . classFun . nameBase)
& lensField .~ lookingupNamer fields
----------------------------------------
-- Fields
-- $deprecatedFields
--
-- This method of optics generation should only be used when migrating an
-- existing codebase from the @lens@ library to @optics@ as it:
--
-- - Doesn't support type changing updates.
--
-- - Introduces tight coupling between types in your application as either all
-- types need to be put in a single module (for @HasX@ class generation to
-- work properly) or there needs to be a single, written by hand module with
-- all the @HasX@ classes the application will use. Both approaches don't
-- scale.
--
-- - Can't be leveraged by libraries because of the above problem lifted to the
-- library level: there would have to exist a library with all possible @HasX@
-- classes written by hand that is imported by all the other
-- libraries. Otherwise for a given @field@ independent libraries would
-- provide multiple @HasField@ classes incompatible with each other.
--
-- See "Optics.Label" for our recommended pattern.
-- | Generate overloaded field accessors.
--
-- /e.g/
--
-- @
-- data Foo a = Foo { _fooX :: 'Int', _fooY :: a }
-- newtype Bar = Bar { _barX :: 'Char' }
-- makeFields ''Foo
-- makeFields ''Bar
-- @
--
-- will create
--
-- @
-- class HasX s a | s -> a where
-- x :: Lens' s a
--
-- instance HasX (Foo a) Int where
-- x = lensVL $ \\f s -> case s of
-- Foo x1 x2 -> fmap (\\y -> Foo y x2) (f x1)
--
-- class HasY s a | s -> a where
-- y :: Lens' s a
--
-- instance HasY (Foo a) a where
-- y = lensVL $ \\f s -> case s of
-- Foo x1 x2 -> fmap (\\y -> Foo x1 y) (f x2)
--
-- instance HasX Bar Char where
-- x = lensVL $ \\f s -> case s of
-- Bar x1 -> fmap (\\y -> Bar y) (f x1)
-- @
--
-- For details, see 'camelCaseFields'.
--
-- @
-- makeFields = 'makeLensesWith' 'defaultFieldRules'
-- @
makeFields :: Name -> DecsQ
makeFields = makeFieldOptics camelCaseFields
-- | Generate overloaded field accessors based on field names which
-- are only prefixed with an underscore (e.g. '_name'), not
-- additionally with the type name (e.g. '_fooName').
--
-- This might be the desired behaviour in case the
-- @DuplicateRecordFields@ language extension is used in order to get
-- rid of the necessity to prefix each field name with the type name.
--
-- As an example:
--
-- @
-- data Foo a = Foo { _x :: 'Int', _y :: a }
-- newtype Bar = Bar { _x :: 'Char' }
-- makeFieldsNoPrefix ''Foo
-- makeFieldsNoPrefix ''Bar
-- @
--
-- will create classes
--
-- @
-- class HasX s a | s -> a where
-- x :: Lens' s a
-- class HasY s a | s -> a where
-- y :: Lens' s a
-- @
--
-- together with instances
--
-- @
-- instance HasX (Foo a) Int
-- instance HasY (Foo a) a where
-- instance HasX Bar Char where
-- @
--
-- For details, see 'classUnderscoreNoPrefixFields'.
--
-- @
-- makeFieldsNoPrefix = 'makeLensesWith' 'classUnderscoreNoPrefixFields'
-- @
makeFieldsNoPrefix :: Name -> DecsQ
makeFieldsNoPrefix = makeFieldOptics classUnderscoreNoPrefixFields
-- | @ declareFields = 'declareLensesWith' 'defaultFieldRules' @
declareFields :: DecsQ -> DecsQ
declareFields = declareLensesWith defaultFieldRules
defaultFieldRules :: LensRules
defaultFieldRules = LensRules
{ _simpleLenses = True
, _generateSigs = True
, _generateClasses = True -- classes will still be skipped if they already exist
, _allowIsos = False -- generating Isos would hinder field class reuse
, _allowUpdates = True
, _lazyPatterns = False
, _classyLenses = const Nothing
, _fieldToDef = camelCaseNamer
}
----------------------------------------
-- Prisms
-- | Generate a 'Control.Lens.Type.Prism' for each constructor of each data type.
--
-- /e.g./
--
-- @
-- declarePrisms [d|
-- data Exp = Lit Int | Var String | Lambda{ bound::String, body::Exp }
-- |]
-- @
--
-- will create
--
-- @
-- data Exp = Lit Int | Var String | Lambda { bound::String, body::Exp }
-- _Lit :: 'Prism'' Exp Int
-- _Var :: 'Prism'' Exp String
-- _Lambda :: 'Prism'' Exp (String, Exp)
-- @
declarePrisms :: DecsQ -> DecsQ
declarePrisms = declareWith $ \dec -> do
emit =<< liftDeclare (makeDecPrisms True dec)
return dec
----------------------------------------
-- Customization of rules
-- | Generate "simple" optics even when type-changing optics are possible.
-- (e.g. 'Lens'' instead of 'Lens')
simpleLenses :: Lens' LensRules Bool
simpleLenses = lensVL $ \f r ->
fmap (\x -> r { _simpleLenses = x}) (f (_simpleLenses r))
-- | Indicate whether or not to supply the signatures for the generated lenses.
--
-- Disabling this can be useful if you want to provide a more restricted type
-- signature or if you want to supply hand-written haddocks.
generateSignatures :: Lens' LensRules Bool
generateSignatures = lensVL $ \f r ->
fmap (\x -> r { _generateSigs = x}) (f (_generateSigs r))
-- | Generate "updateable" optics when 'True'. When 'False', (affine) folds will
-- be generated instead of (affine) traversals and getters will be generated
-- instead of lenses. This mode is intended to be used for types with invariants
-- which must be maintained by "smart" constructors.
generateUpdateableOptics :: Lens' LensRules Bool
generateUpdateableOptics = lensVL $ \f r ->
fmap (\x -> r { _allowUpdates = x}) (f (_allowUpdates r))
-- | Generate optics using lazy pattern matches. This can
-- allow fields of an undefined value to be initialized with lenses:
--
-- @
-- data Foo = Foo {_x :: Int, _y :: Bool}
-- deriving Show
--
-- 'makeLensesWith' ('lensRules' & 'generateLazyPatterns' .~ True) ''Foo
-- @
--
-- @
-- > undefined & x .~ 8 & y .~ True
-- Foo {_x = 8, _y = True}
-- @
--
-- The downside of this flag is that it can lead to space-leaks and
-- code-size/compile-time increases when generated for large records. By default
-- this flag is turned off, and strict optics are generated.
--
-- When using lazy optics the strict optic can be recovered by composing with
-- 'equality'':
--
-- @
-- strictOptic = equality' % lazyOptic
-- @
generateLazyPatterns :: Lens' LensRules Bool
generateLazyPatterns = lensVL $ \f r ->
fmap (\x -> r { _lazyPatterns = x}) (f (_lazyPatterns r))
-- | Create the class if the constructor if generated lenses would be
-- type-preserving and the 'lensClass' rule matches.
createClass :: Lens' LensRules Bool
createClass = lensVL $ \f r ->
fmap (\x -> r { _generateClasses = x}) (f (_generateClasses r))
-- | 'Lens'' to access the convention for naming fields in our 'LensRules'.
lensField :: Lens' LensRules FieldNamer
lensField = lensVL $ \f r ->
fmap (\x -> r { _fieldToDef = x}) (f (_fieldToDef r))
-- | 'Lens'' to access the option for naming "classy" lenses.
lensClass :: Lens' LensRules ClassyNamer
lensClass = lensVL $ \f r ->
fmap (\x -> r { _classyLenses = x }) (f (_classyLenses r))
----------------------------------------
-- Common sets of rules
-- | Field rules for fields without any prefix. Useful for generation of field
-- labels when paired with @DuplicateRecordFields@ language extension so that no
-- prefixes for field names are necessary.
--
-- @since 0.2
noPrefixFieldLabels :: LensRules
noPrefixFieldLabels = fieldLabelsRules { _fieldToDef = noPrefixNamer }
abbreviatedFieldLabels :: LensRules
abbreviatedFieldLabels = fieldLabelsRules { _fieldToDef = abbreviatedNamer }
-- | Field rules for fields in the form @ _prefix_fieldname @
underscoreFields :: LensRules
underscoreFields = defaultFieldRules & lensField .~ underscoreNamer
-- | Field rules for fields in the form @ prefixFieldname or _prefixFieldname @
--
-- If you want all fields to be lensed, then there is no reason to use an @_@
-- before the prefix. If any of the record fields leads with an @_@ then it is
-- assume a field without an @_@ should not have a lens created.
--
-- __Note__: The @prefix@ must be the same as the typename (with the first
-- letter lowercased). This is a change from lens versions before lens 4.5. If
-- you want the old behaviour, use 'makeLensesWith' 'abbreviatedFields'
camelCaseFields :: LensRules
camelCaseFields = defaultFieldRules
-- | Field rules for fields in the form @ _fieldname @ (the leading
-- underscore is mandatory).
--
-- __Note__: The primary difference to 'camelCaseFields' is that for
-- @classUnderscoreNoPrefixFields@ the field names are not expected to
-- be prefixed with the type name. This might be the desired behaviour
-- when the @DuplicateRecordFields@ extension is enabled.
classUnderscoreNoPrefixFields :: LensRules
classUnderscoreNoPrefixFields =
defaultFieldRules & lensField .~ classUnderscoreNoPrefixNamer
-- | Field rules fields in the form @ prefixFieldname or _prefixFieldname @
-- If you want all fields to be lensed, then there is no reason to use an @_@ before the prefix.
-- If any of the record fields leads with an @_@ then it is assume a field without an @_@ should not have a lens created.
--
-- Note that @prefix@ may be any string of characters that are not uppercase
-- letters. (In particular, it may be arbitrary string of lowercase letters
-- and numbers) This is the behavior that 'defaultFieldRules' had in lens
-- 4.4 and earlier.
abbreviatedFields :: LensRules
abbreviatedFields = defaultFieldRules { _fieldToDef = abbreviatedNamer }
----------------------------------------
-- Namers
-- | A 'FieldNamer' that leaves the field name as-is. Useful for generation of
-- field labels when paired with @DuplicateRecordFields@ language extension so
-- that no prefixes for field names are necessary.
--
-- @since 0.2
noPrefixNamer :: FieldNamer
noPrefixNamer _ _ n = [TopName n]
-- | A 'FieldNamer' that strips the _ off of the field name, lowercases the
-- name, and skips the field if it doesn't start with an '_'.
underscoreNoPrefixNamer :: FieldNamer
underscoreNoPrefixNamer _ _ n =
case nameBase n of
'_':x:xs -> [TopName (mkName (toLower x:xs))]
_ -> []
-- | Create a 'FieldNamer' from explicit pairings of @(fieldName, lensName)@.
lookingupNamer :: [(String,String)] -> FieldNamer
lookingupNamer kvs _ _ field =
[ TopName (mkName v) | (k,v) <- kvs, k == nameBase field]
-- | Create a 'FieldNamer' from a mapping function. If the function returns
-- @[]@, it creates no lens for the field.
mappingNamer :: (String -> [String]) -- ^ A function that maps a @fieldName@ to
-- @lensName@s.
-> FieldNamer
mappingNamer mapper _ _ = fmap (TopName . mkName) . mapper . nameBase
-- | A 'FieldNamer' for 'underscoreFields'.
underscoreNamer :: FieldNamer
underscoreNamer _ _ field = maybeToList $ do
_ <- prefix field'
method <- niceLens
cls <- classNaming
return (MethodName (mkName cls) (mkName method))
where
field' = nameBase field
prefix ('_':xs) | '_' `L.elem` xs = Just (takeWhile (/= '_') xs)
prefix _ = Nothing
niceLens = prefix field' <&> \n -> drop (length n + 2) field'
classNaming = niceLens <&> ("Has_" ++)
-- | A 'FieldNamer' for 'camelCaseFields'.
camelCaseNamer :: FieldNamer
camelCaseNamer tyName fields field = maybeToList $ do
fieldPart <- L.stripPrefix expectedPrefix (nameBase field)
method <- computeMethod fieldPart
let cls = "Has" ++ fieldPart
return (MethodName (mkName cls) (mkName method))
where
expectedPrefix = optUnderscore ++ over _head toLower (nameBase tyName)
optUnderscore = ['_' | any (L.isPrefixOf "_" . nameBase) fields ]
computeMethod (x:xs) | isUpper x = Just (toLower x : xs)
computeMethod _ = Nothing
-- | A 'FieldNamer' for 'classUnderscoreNoPrefixFields'.
classUnderscoreNoPrefixNamer :: FieldNamer
classUnderscoreNoPrefixNamer _ _ field = maybeToList $ do
fieldUnprefixed <- L.stripPrefix "_" (nameBase field)
let className = "Has" ++ over _head toUpper fieldUnprefixed
methodName = fieldUnprefixed
return (MethodName (mkName className) (mkName methodName))
-- | A 'FieldNamer' for 'abbreviatedFields'.
abbreviatedNamer :: FieldNamer
abbreviatedNamer _ fields field = maybeToList $ do
fieldPart <- stripMaxLc (nameBase field)
method <- computeMethod fieldPart
let cls = "Has" ++ fieldPart
return (MethodName (mkName cls) (mkName method))
where
stripMaxLc f = do x <- L.stripPrefix optUnderscore f
case break isUpper x of
(p,s) | L.null p || L.null s -> Nothing
| otherwise -> Just s
optUnderscore = ['_' | any (L.isPrefixOf "_" . nameBase) fields ]
computeMethod (x:xs) | isUpper x = Just (toLower x : xs)
computeMethod _ = Nothing
----------------------------------------
-- Internal TH Implementation
-- Declaration quote stuff
declareWith :: (Dec -> Declare Dec) -> DecsQ -> DecsQ
declareWith fun = (runDeclare . traverseDataAndNewtype fun =<<)
-- | Monad for emitting top-level declarations as a side effect. We also track
-- the set of field class 'Name's that have been created and consult them to
-- avoid creating duplicate classes.
-- See #463 for more information.
type Declare = WriterT (Endo [Dec]) (StateT (Set Name) Q)
liftDeclare :: Q a -> Declare a
liftDeclare = lift . lift
runDeclare :: Declare [Dec] -> DecsQ
runDeclare dec = do
(out, endo) <- evalStateT (runWriterT dec) Set.empty
return $ out ++ appEndo endo []
emit :: [Dec] -> Declare ()
emit decs = tell $ Endo (decs++)
-- | Traverse each data, newtype, data instance or newtype instance
-- declaration.
traverseDataAndNewtype :: (Applicative f) => (Dec -> f Dec) -> [Dec] -> f [Dec]
traverseDataAndNewtype f decs = traverse go decs
where
go dec = case dec of
DataD{} -> f dec
NewtypeD{} -> f dec
DataInstD{} -> f dec
NewtypeInstD{} -> f dec
-- Recurse into instance declarations because they main contain
-- associated data family instances.
InstanceD moverlap ctx inst body -> InstanceD moverlap ctx inst <$> traverse go body
_ -> pure dec
stripFields :: Dec -> Dec
stripFields dec = case dec of
DataD ctx tyName tyArgs kind cons derivings ->
DataD ctx tyName tyArgs kind (map deRecord cons) derivings
NewtypeD ctx tyName tyArgs kind con derivings ->
NewtypeD ctx tyName tyArgs kind (deRecord con) derivings
DataInstD ctx tyName tyArgs kind cons derivings ->
DataInstD ctx tyName tyArgs kind (map deRecord cons) derivings
NewtypeInstD ctx tyName tyArgs kind con derivings ->
NewtypeInstD ctx tyName tyArgs kind (deRecord con) derivings
_ -> dec
deRecord :: Con -> Con
deRecord con@NormalC{} = con
deRecord con@InfixC{} = con
deRecord (ForallC tyVars ctx con) = ForallC tyVars ctx $ deRecord con
deRecord (RecC conName fields) = NormalC conName (map dropFieldName fields)
deRecord con@GadtC{} = con
deRecord (RecGadtC ns fields retTy) = GadtC ns (map dropFieldName fields) retTy
dropFieldName :: VarBangType -> BangType
dropFieldName (_, str, typ) = (str, typ)