Pretty self-explanatory. I know that makeClassy
should create typeclasses, but I see no difference between the two.
PS. Bonus points for explaining the default behaviour of both.
Note: This answer is based on lens 4.4 or newer. There were some changes to the TH in that version, so I don't know how much of it applies to older versions of lens.
The lens TH functions are all based on one function, makeLensesWith
(also named makeFieldOptics
inside lens). This function takes a LensRules
argument, which describes exactly what is generated and how.
So to compare makeLenses
and makeFields
, we only need to compare the LensRules
that they use. You can find them by looking at the source:
lensRules :: LensRules
lensRules = LensRules
{ _simpleLenses = False
, _generateSigs = True
, _generateClasses = False
, _allowIsos = True
, _classyLenses = const Nothing
, _fieldToDef = \_ n ->
case nameBase n of
'_':x:xs -> [TopName (mkName (toLower x:xs))]
_ -> []
}
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
, _classyLenses = const Nothing
, _fieldToDef = camelCaseNamer
}
Now we know that the differences are in the simpleLenses
, generateClasses
, allowIsos
and fieldToDef
options. But what do those options actually mean?
makeFields
will never generate type-changing optics. This is controlled by the simpleLenses = True
option. That option doesn't have haddocks in the current version of lens. However, lens HEAD added documentation for it:
-- | Generate "simple" optics even when type-changing optics are possible.
-- (e.g. 'Lens'' instead of 'Lens')
So makeFields
will never generate type-changing optics, while makeLenses
will if possible.
makeFields
will generate classes for the fields. So for each field foo
, we have a class:
class HasFoo t where
foo :: Lens' t <Type of foo field>
This is controlled by the generateClasses
option.
makeFields
will never generate Iso
's, even if that would be possible (controlled by the allowIsos
option, which doesn't seem to be exported from Control.Lens.TH
)
While makeLenses
simply generates a top-level lens for each field that starts with an underscore (lowercasing the first letter after the underscore), makeFields
will instead generate instances for the HasFoo
classes. It also uses a different naming scheme, explained in a comment in the source code:
-- | 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.
camelCaseFields :: LensRules
camelCaseFields = defaultFieldRules
So makeFields
also expect that all fields are not just prefixed with an underscore, but also include the data type name as a prefix (as in data Foo = { _fooBar :: Int, _fooBaz :: Bool }
). If you want to generate lenses for all fields, you can leave out the underscore.
This is all controlled by the _fieldToDef
(exported as lensField
by Control.Lens.TH
).
As you can see, the Control.Lens.TH
module is very flexible. Using makeLensesWith
, you can create your very own LensRules
if you need a pattern not covered by the standard functions.