带有 makeFields 的字段
(此示例从此 StackOverflow 应答复制 )
假设你有许多不同的数据类型,它们都应该具有相同名称的镜头,在本例中为 capacity
。makeFields
切片将创建一个无需命名空间冲突即可实现此目的的类。
{-# 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
类,其中容量是从 s
到 a
的 Lens'
(一旦知道了 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
可以向现有类添加更多实例,并将你的类型分布在多个模块中。但是如果你在另一个没有导入类的模块中再次使用它,它将创建一个新的类(具有相同的名称),并且你将有两个不兼容的重载容量镜头。