鏡頭提供了一種通用的方式來引用數據類型字段,以便在不破壞向後兼容性的情況下擴展或重構數據集。我將使用lens-family
和lens-family-th
庫來說明這一點,因爲它們比lens
更輕依賴。
首先,讓我們用一個簡單的記錄有兩個字段:
{-# LANGUAGE Template Haskell #-}
import Lens.Family2
import Lens.Family2.TH
data Example = Example
{ _int :: Int
, _str :: String
}
makeLenses ''Example
-- This creates these lenses:
int :: Lens' Example Int
str :: Lens' Example String
現在你可以寫State
FUL代碼引用您的數據結構的字段。您可以使用Lens.Family2.State.Strict
用於此目的:
import Lens.Family2.State.Strict
-- Everything here also works for `StateT Example IO`
example :: State Example Bool
example = do
s <- use str -- Read the `String`
str .= s ++ "!" -- Set the `String`
int += 2 -- Modify the `Int`
zoom int $ do -- This sub-`do` block has type: `State Int Int`
m <- get
return (m + 1)
要注意的關鍵問題是,我可以更新我的數據類型,和上面的代碼仍然可以編譯。一個新字段添加到Example
,一切都將仍然工作:
data Example = Example
{ _int :: Int
, _str :: String
, _char :: Char
}
makeLenses ''Example
int :: Lens' Example Int
str :: Lens' Example String
char :: Lens' Example Char
但是,我們其實可以更進一步,完全重構我們的Example
類型是這樣的:
data Example = Example
{ _example2 :: Example
, _char :: Char
}
data Example2 = Example2
{ _int2 :: Int
, _str2 :: String
}
makeLenses ''Example
char :: Lens' Example Char
example2 :: Lens' Example Example2
makeLenses ''Example2
int2 :: Lens' Example2 Int
str2 :: Lens' Example2 String
難道我們必須打破我們舊代碼?沒有!我們所要做的就是添加以下兩個鏡頭,支持向後兼容性:,
int :: Lens' Example Int
int = example2 . int2
str :: Lens' Example Char
str = example2 . str2
現在所有的舊代碼仍然有效,沒有任何變化,儘管我們Example
類型的侵入重構。
實際上,這不僅僅適用於記錄。對於和類型也可以做同樣的事情(也稱爲代數數據類型或枚舉)。例如,假設我們有這種類型的:
data Example3 = A String | B Int
makeTraversals ''Example3
-- This creates these `Traversals'`:
_A :: Traversal' Example3 String
_B :: Traversal' Example3 Int
許多我們與和類型做的事情同樣可以在Traversal'
條款再表達。模式匹配有一個明顯的例外:它實際上可以通過使用總體檢查來實現模式匹配,但是它目前是冗長的。
但是,同樣的觀點成立:如果您用Traversal'
s表示所有總和類型操作,那麼您可以大大重構您的總和類型,只需更新適當的Traversal'
即可,以保持向後兼容性。
最後:請注意sum類型構造函數的真實模擬是Prism
s(它允許您使用構造函數除模式匹配外還構建值)。這些不受lens-family
系列庫的支持,但是它們由lens
提供,如果需要,您可以使用profunctors
依賴關係自行實施它們。此外,如果您想知道新類型的lens
模擬是什麼,它是Iso'
,並且最低限度需要profunctors
依賴關係。
此外,我所說的一切都適用於遞歸類型的多個字段(使用Fold
s)。從字面上看,任何您想要以向後兼容的方式引用數據類型的內容都包含在lens
庫中。
你說你不喜歡使用鏡頭,但這正是鏡頭解決問題的那種問題 – 2014-10-06 23:31:58
請注意,WAI的設計目標可能與您的目標完全不同。我傾向於在這裏同意Gabriel,這就是*不常使用鏡頭的人。 – 2014-10-07 07:02:17
@MichaelSnoyman其實,我不知道我想要什麼。一些東西進出,控制其輸入對於簡約用戶界面來說已經足夠了。中間件定義似乎足夠了。應用程序是UI的核心。通常的用戶不寫應用程序。這是一個不同的目的,也許不是最好的方法,但關於中間件的問題就在那裏。 – Vektorweg 2014-10-07 10:51:07