非常不言自明。我知道makeClassy
应该创建类型类,但我发现两者之间没有区别。
PS。用于解释两者默认行为的加分点。
答案 0 :(得分:13)
注意:此答案基于镜头4.4或更新版本。该版本的TH有一些变化,所以我不知道它有多少适用于老版本的镜头。
镜头TH功能全部基于一个功能makeLensesWith
(镜头内部也称为makeFieldOptics
)。此函数采用LensRules
参数,该参数准确描述了生成的内容和方式。
因此,为了比较makeLenses
和makeFields
,我们只需要比较他们使用的LensRules
。您可以通过查看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
}
现在我们知道差异在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
会生成{{1}的实例1}}类。它还使用了不同的命名方案,在源代码的注释中进行了解释:
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
。
如您所见,Control.Lens.TH
模块非常灵活。使用Control.Lens.TH
,如果您需要标准函数未涵盖的模式,则可以创建自己的makeLensesWith
。
答案 1 :(得分:3)
免责声明:这是基于对工作代码的试验;它给了我足够的信息来继续我的项目,但我仍然更喜欢更好的答案。
data Stuff = Stuff {
_foo
_FooBar
_stuffBaz
}
makeLenses
foo
创建为Stuff
fooBar
(将大写名称更改为小写); makeFields
baz
和一个类HasBaz
;它会使Stuff
成为该类的一个实例。答案 2 :(得分:3)
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
为每个字段创建一个单独的类,旨在在具有给定名称的字段的所有类型之间重用。这更像是一种记录字段名称无法在类型之间共享的解决方案。