Ответ 1
Примечание. Этот ответ основан на объективе 4.4 или новее. В этой версии были внесены некоторые изменения в TH, поэтому я не знаю, сколько из них относится к более старым версиям объектива.
Организация функций объектива TH
Функции объектива TH основаны на одной функции, makeLensesWith
(также называемой makeFieldOptics
внутри объектива). Эта функция принимает аргумент LensRules
, который точно описывает, что генерируется и как.
Итак, для сравнения makeLenses
и makeFields
нам нужно сравнить только те LensRules
, которые они используют. Вы можете найти их, посмотрев источник :
makeLenses
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))]
_ -> []
}
makeFields
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
}
Что это значит?
Теперь мы знаем, что различия находятся в параметрах simpleLenses
, generateClasses
, allowIsos
и fieldToDef
. Но что на самом деле означают эти варианты?
-
makeFields
никогда не будет генерировать изменяющие тип оптики. Это контролируется опциейsimpleLenses = True
. Этот вариант не имеет пик в текущей версии объектива. Однако объектив HEAD добавил для него документацию:-- | Generate "simple" optics even when type-changing optics are possible. -- (e.g. 'Lens'' instead of 'Lens')
Итак
makeFields
никогда не будет генерировать изменяющую тип оптики, аmakeLenses
, если возможно. -
makeFields
будет генерировать классы для полей. Итак, для каждого поляfoo
мы имеем класс:class HasFoo t where foo :: Lens' t <Type of foo field>
Это управляется опцией
generateClasses
. -
makeFields
никогда не будет генерироватьIso
, даже если это возможно (управляется параметромallowIsos
, который, кажется, не экспортируется изControl.Lens.TH
) -
В то время как
makeLenses
просто генерирует объектив верхнего уровня для каждого поля, начинающегося с подчеркивания (нижний индекс первой буквы после подчеркивания),makeFields
вместо этого генерирует экземпляры для классовHasFoo
. Он также использует другую схему именования, объясненную в комментарии в исходном коде:-- | 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 @[email protected] before the prefix. -- If any of the record fields leads with an @[email protected] then it is assume a field without an @[email protected] should not have a lens created. camelCaseFields :: LensRules camelCaseFields = defaultFieldRules
Итак,
makeFields
также ожидает, что все поля не просто префиксны с подчеркиванием, но также включают имя типа данных в качестве префикса (как вdata Foo = { _fooBar :: Int, _fooBaz :: Bool }
). Если вы хотите создать объективы для всех полей, вы можете оставить символ подчеркивания.Все это управляется
_fieldToDef
(экспортируется какlensField
наControl.Lens.TH
).
Как вы можете видеть, модуль Control.Lens.TH
очень гибкий. Используя makeLensesWith
, вы можете создать свой собственный LensRules
, если вам нужен шаблон, не охватываемый стандартными функциями.