我发现文档也有点不清楚; 必须弄清楚Control.Lens.TH通过实验做了多少事.
你想要的是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的透镜'(a一旦知道就固定).它通过从字段中剥离数据类型的(小写的)名称来找出名称"capcity"; 我觉得不必在字段名称或镜头名称上使用下划线,因为有时记录语法实际上就是你想要的.您可以使用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.
所以第一个splace创建了HasCapcity类并为Foo添加了一个实例; 第二个使用现有的类并为Bar创建了一个实例.
如果从另一个模块导入HasCapcity类,这也有效; makeFields可以向现有类添加更多实例,并将您的类型分散到多个模块中.但是如果你在没有导入类的另一个模块中再次使用它,它将创建一个新的类(具有相同的名称),并且你将有两个capacity
不兼容的重载镜头.
makeClassy有点不同.如果我有:
data Foo = Foo { _capacity :: Int } deriving (Eq, Show) $(makeClassy ''Foo)
(注意到makeClassy更喜欢在字段上使用下划线前缀,而不是数据类型名称)
然后,再次使用-ddump-splices:
[1 of 1] Compiling Foo ( Foo.hs, interpreted ) Foo.hs:14:3-18: Splicing declarations makeClassy ''Foo ======> class HasFoo c_a85j where foo :: Lens' c_a85j Foo capacity :: Lens' c_a85j Int {-# INLINE capacity #-} capacity = (.) foo capacity instance HasFoo Foo where {-# INLINE capacity #-} foo = id capacity = iso (\ (Foo x_a85k) -> x_a85k) Foo Ok, modules loaded: Foo.
它创建的类是HasFoo,而不是HasCapacity; 它说任何你可以获得Foo的东西,你也可以获得Foo的能力.并且该类硬编码表示城市是一个Int
,而不是像你一样超载它makeFields
.所以这仍然有效(因为HasFoo Foo
,你只是通过使用获得Foo id
):
*Foo ? let f = Foo 3 | f :: Foo *Foo ? f ^. capacity 3 it :: Int
但是你不能使用这个capcity
镜头来获得不相关类型的容量.
我发现文档也有点不清楚; 必须弄清楚Control.Lens.TH通过实验做了多少事.
你想要的是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的透镜'(a一旦知道就固定).它通过从字段中剥离数据类型的(小写的)名称来找出名称"capcity"; 我觉得不必在字段名称或镜头名称上使用下划线,因为有时记录语法实际上就是你想要的.您可以使用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.
所以第一个splace创建了HasCapcity类并为Foo添加了一个实例; 第二个使用现有的类并为Bar创建了一个实例.
如果从另一个模块导入HasCapcity类,这也有效; makeFields可以向现有类添加更多实例,并将您的类型分散到多个模块中.但是如果你在没有导入类的另一个模块中再次使用它,它将创建一个新的类(具有相同的名称),并且你将有两个capacity
不兼容的重载镜头.
makeClassy有点不同.如果我有:
data Foo = Foo { _capacity :: Int } deriving (Eq, Show) $(makeClassy ''Foo)
(注意到makeClassy更喜欢在字段上使用下划线前缀,而不是数据类型名称)
然后,再次使用-ddump-splices:
[1 of 1] Compiling Foo ( Foo.hs, interpreted ) Foo.hs:14:3-18: Splicing declarations makeClassy ''Foo ======> class HasFoo c_a85j where foo :: Lens' c_a85j Foo capacity :: Lens' c_a85j Int {-# INLINE capacity #-} capacity = (.) foo capacity instance HasFoo Foo where {-# INLINE capacity #-} foo = id capacity = iso (\ (Foo x_a85k) -> x_a85k) Foo Ok, modules loaded: Foo.
它创建的类是HasFoo,而不是HasCapacity; 它说任何你可以获得Foo的东西,你也可以获得Foo的能力.并且该类硬编码表示城市是一个Int
,而不是像你一样超载它makeFields
.所以这仍然有效(因为HasFoo Foo
,你只是通过使用获得Foo id
):
*Foo ? let f = Foo 3 | f :: Foo *Foo ? f ^. capacity 3 it :: Int
但是你不能使用这个capcity
镜头来获得不相关类型的容量.