Довольно понятно. Я знаю, что makeClassy должен создавать классы, но я не вижу разницы между ними.
PS. Бонусные баллы для объяснения поведения по умолчанию обоих.
Довольно понятно. Я знаю, что makeClassy должен создавать классы, но я не вижу разницы между ними.
PS. Бонусные баллы для объяснения поведения по умолчанию обоих.
Примечание. Этот ответ основан на объективе 4.4 или новее. В этой версии были внесены некоторые изменения в TH, поэтому я не знаю, сколько из них относится к более старым версиям объектива.
Функции объектива TH основаны на одной функции, makeLensesWith (также называемой makeFieldOptics внутри объектива). Эта функция принимает аргумент LensRules, который точно описывает, что генерируется и как.
Итак, для сравнения makeLenses и makeFields нам нужно сравнить только те LensRules, которые они используют. Вы можете найти их, посмотрев источник :
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
}
Теперь мы знаем, что различия находятся в параметрах 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, если вам нужен шаблон, не охватываемый стандартными функциями.
Отказ от ответственности: это основано на эксперименте с рабочим кодом; он дал мне достаточно информации для продолжения моего проекта, но я все же предпочел бы лучше документированный ответ.
data Stuff = Stuff {
_foo
_FooBar
_stuffBaz
}
makeLensesfoo в качестве объектива для доступа к StufffooBar (изменение имени с заглавной буквы в нижний регистр);makeFieldsbaz и класс HasBaz; он сделает Stuff экземпляр этого класса. makeLenses создает единственную оптику верхнего уровня для каждого поля в типе. Он ищет поля, начинающиеся с символа подчеркивания (_), и он создает оптический объект, который является настолько общим, насколько это возможно для этого поля.
Iso.Lens.Traversal. makeClassy создает один класс, содержащий всю оптику для вашего типа. Эта версия используется для упрощения встраивания вашего типа в другой более крупный тип, достигающий своего рода подтипирования. Опция Lens и Traversal будет создана в соответствии с вышеприведенными правилами (исключается Iso, поскольку это препятствует поведению подтипирования.)
В дополнение к одному методу в классе для каждого поля вы получите дополнительный метод, который упрощает вывод экземпляров этого класса для других типов. Все другие методы имеют экземпляры по умолчанию с точки зрения метода верхнего уровня.
data T = MkT { _field1 :: Int, _field2 :: Char }
class HasT a where
t :: Lens' a T
field1 :: Lens' a Int
field2 :: Lens' a Char
field1 = t . field1
field2 = t . field2
instance HasT T where
t = id
field1 f (MkT x y) = fmap (\x' -> MkT x' y) (f x)
field2 f (MkT x y) = fmap (\y' -> MkT x y') (f y)
data U = MkU { _subt :: T, _field3 :: Bool }
instance HasT U where
t f (MkU x y) = fmap (\x' -> MkU x' y) (f x)
-- field1 and field2 automatically defined
Это имеет дополнительное преимущество, что легко экспортировать/импортировать все объективы для данного типа. import Module (HasT(..))
makeFields создает один класс для каждого поля, который предназначен для повторного использования между всеми типами, у которых есть поле с заданным именем. Это скорее решение для записи имен полей, которые не могут быть разделены между типами.