当前位置:  开发笔记 > 编程语言 > 正文

使用makeClassy使用相同的字段名称制作镜头(TH)

如何解决《使用makeClassy使用相同的字段名称制作镜头(TH)》经验,为你挑选了1个好方法。

我发现文档也有点不清楚; 必须弄清楚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镜头来获得不相关类型的容量.



1> Ben..:

我发现文档也有点不清楚; 必须弄清楚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镜头来获得不相关类型的容量.

推荐阅读
路人甲
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有