很不言自明。我知道makeClassy应该创建类型类,但是我发现两者之间没有区别。

PS。解释这两者的默认行为的加分点。

最佳答案

注意:此答案基于镜头4.4或更高版本。该版本的TH进行了一些更改,因此我不知道其中有多少适用于旧版镜头。

镜头TH功能的组织

镜头TH功能全部基于一个功能makeLensesWith(在镜头内部也称为makeFieldOptics)。此函数采用LensRules参数,该参数精确描述了生成的内容和方式。

因此,要比较makeLensesmakeFields,我们只需要比较它们使用的LensRules。您可以通过查看source找到它们:

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
  }

这些是什么意思?

现在我们知道了simpleLensesgenerateClassesallowIsosfieldToDef选项的区别。但是这些选择实际上意味着什么?
  • 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 @_@ 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创建自己的LensRules

    10-06 02:45