很不言自明。我知道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
选项控制。该选项在当前版本的镜头中没有黑线鳕。但是,lens 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
's,即使那是可能的(由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 @_@ 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
所以makeFields
也期望所有字段不只是以下划线为前缀,还包括数据类型名称作为前缀(如 中data Foo = { _fooBar :: Int, _fooBaz :: Bool }
)。如果要为所有领域生成镜头,可以省略下划线。
这一切都由_fieldToDef
(导出为lensField
by 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
为每个字段创建一个类,该类旨在在具有给定名称的字段的所有类型之间重用。这更像是记录无法在类型之间共享的字段名称的解决方案。