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