Довольно понятно. Я знаю, что 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
}
makeLenses
foo
в качестве объектива для доступа к Stuff
fooBar
(изменение имени с заглавной буквы в нижний регистр);makeFields
baz
и класс 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
создает один класс для каждого поля, который предназначен для повторного использования между всеми типами, у которых есть поле с заданным именем. Это скорее решение для записи имен полей, которые не могут быть разделены между типами.