makeLenses和makeFields之间有什么区别?

时间:2014-08-30 18:40:59

标签: haskell lens

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

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

3 个答案:

答案 0 :(得分:13)

注意:此答案基于镜头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会生成{{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创建一个包含所有类型光学元件的类。此版本用于简化您的类型嵌入另一个更大类型实现一种子类型。将根据上述规则创建LensTraversal光学器件(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为每个字段创建一个单独的类,旨在在具有给定名称的字段的所有类型之间重用。这更像是一种记录字段名称无法在类型之间共享的解决方案。