帶有 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 可以向現有類新增更多例項,並將你的型別分佈在多個模組中。但是如果你在另一個沒有匯入類的模組中再次使用它,它將建立一個新的類(具有相同的名稱),並且你將有兩個不相容的過載容量鏡頭。