带有 makeFields 的字段

(此示例从此 StackOverflow 应答复制 )

假设你有许多不同的数据类型,它们都应该具有相同名称的镜头,在本例中为 capacitymakeFields 切片将创建一个无需命名空间冲突即可实现此目的的类。

{-# LANGUAGE FunctionalDependencies
           , MultiParamTypeClasses
           , TemplateHaskell
  #-}

module Foo
where

import Control.Lens

data Foo
  = Foo { fooCapacity::Int }
  deriving (Eq, Show)
$(makeFields ''Foo)

data Bar
  = Bar { barCapacity::Double }
  deriving (Eq, Show)
$(makeFields ''Bar)

然后在 ghci:

*Foo
λ let f = Foo 3
|     b = Bar 7
| 
b::Bar
f::Foo

*Foo
λ fooCapacity f
3
it::Int

*Foo
λ barCapacity b
7.0
it::Double

*Foo
λ f ^. capacity
3
it::Int

*Foo
λ b ^. capacity
7.0
it::Double

λ :info HasCapacity 
class HasCapacity s a | s -> a where
  capacity::Lens' s a
    -- Defined at Foo.hs:14:3
instance HasCapacity Foo Int -- Defined at Foo.hs:14:3
instance HasCapacity Bar Double -- Defined at Foo.hs:19:3

所以它实际上做了什么被声明为 HasCapacity s a 类,其中容量是从 saLens'(一旦知道了 a 就固定了)。它通过从字段中剥离数据类型的(小写的)名称来找出名称容量; 我觉得不必在字段名称或镜头名称上使用下划线,因为有时记录语法实际上就是你想要的。你可以使用 makeFieldsWith 和各种 lensRules 来计算镜头名称。

如果它有帮助,使用 ghci -ddump-splices Foo.hs:

[1 of 1] Compiling Foo              ( Foo.hs, interpreted )
Foo.hs:14:3-18: Splicing declarations
    makeFields ''Foo
  ======>
    class HasCapacity s a | s -> a where
      capacity::Lens' s a
    instance HasCapacity Foo Int where
      {-# INLINE capacity #-}
      capacity = iso (\ (Foo x_a7fG) -> x_a7fG) Foo
Foo.hs:19:3-18: Splicing declarations
    makeFields ''Bar
  ======>
    instance HasCapacity Bar Double where
      {-# INLINE capacity #-}
      capacity = iso (\ (Bar x_a7ne) -> x_a7ne) Bar
Ok, modules loaded: Foo.

所以第一个拼接使得 HasCapcity 类为 Foo 添加了一个实例; 第二个使用现有的类并为 Bar 创建了一个实例。

如果从另一个模块导入 HasCapcity 类,这也有效; makeFields 可以向现有类添加更多实例,并将你的类型分布在多个模块中。但是如果你在另一个没有导入类的模块中再次使用它,它将创建一个新的类(具有相同的名称),并且你将有两个不兼容的重载容量镜头。