如何理清 lens 這個庫的各個組件,熟悉各種高級玩法?
比如說,有沒有一套習題可以讓人來逐步掌握?需要哪些基礎的代數知識?
要理清各種組件,得先熟悉 lens 里最核心概念的表示方法。先看 Twan van Laarhoven 最初提出用 Rank-2 類型編碼 lens 的文章:CPS based functional references,以及他的另一個相關的talk:Talk on Lenses。lens 里的 Lens 其實就是 Generalized van Laarhoven lens(多帶了2個額外的類型參數)
接下來可以學習 microlens 的代碼和文檔。microlens 的類型和 lens 庫的類型是兼容的,後者是 batteries-included 的版本,前者則 footprint 更小,對初學者也更友好。使用 Rank-2 類型編碼 lens 的一大好處就是可以模擬一個簡單的 subtyping hierarchy,比如需要 Getter/Setter 的函數可以直接傳一個完整的 Lens,如果是其他的 lens 編碼方法,還需要手動的 coercion。(完整的 hierarchy 參見 Lenses, Folds and Traversals 那張大圖
理解了 lens 相關的幾種數據類型的原理,會自己實現以後就沒什麼難的了,多用 lens 庫里的各種組合子熟悉就好了。日常 Haskell 編程里最直接的好處,除了操作 nested data structure 特別優雅以外,對許多常見類型的操作也有了統一的 interface,免得一堆 import qualified 。。。像 Michael Snoyman 的 mono-traversable 和衍生的替代標準庫 classy-prelude,一個核心初衷就是搞各種數據結構操作的統一界面,而在這一點上私以為 lens 做得優雅多了(自問自答一下,主要是幫助初學者。我本人是想看到一些中級及以上的教程。
所謂的 lens,就是對一個數據結構的一部分的引用:
type Lens s a = ...
view :: Lens s a -&> s -&> a
put :: Lens s a -&> a -&> s -&> s
這裡的 view 和 put 分別用來訪問 s 的某一部分 a 或者更新那一部分。
比較有趣的一點是 lens 可以組合的,比如說:fstLens :: Lens (a, b) a
view fstLens (3, "foo") = 3
put fstLens 4 (3, "foo") = (4, "foo")
composeLens :: Lens a b -&> Lens b c -&> Lens a c
view (fstLens `composeLens` fstLens) ((1, 2), 3) = 1
data Lens s a =
{ view :: s -&> a
, put :: a -&> s -&> a
}
type Lens s a = forall f. Functor f =&> (a -&> f a) -&> (s -&> f s)
注意,這裡需要打開 RankNTypes 擴展。view 和 put 的實現分別可以藉助 Const 與 Identity 函子,此處留做練習。讀者也可以嘗試計算一下這種定義方式下的組合函數是什麼。
如果覺得我講的太快了,可以參考 SPJ 的 lens 的一個教程。理解完這段內容之後,就是 Prism 和 Iso 了,也是我這個問題所關心的。
黑箱之外官方一圖流
箭頭表示屬於
示例:
所有的Iso都是Lens
所有的Prism都是Traversal
每個b=a, t=s的Lens都是一個Getter
Lens等類型是可以組合的
(.) :: Lens s t a b -&> Lens a b x y -&> Lens s t x y
(.) :: Iso s t a b -&> Iso a b x y -&> Iso s t x y
類型之間也是可以組合的, 組合完成的類型是他們共同所屬的類
(.) :: Lens s t a b -&> Prism a b x y -&> Traversal s t x y
因為Lens是Traversal, Prism也是Traversal, 那麼組合Lens和Prism也是Traversal
通過圖上提供的函數來構建Lens/Traversal/Iso/Prism:
Lens = Getter + Setter
lens :: (s -&> a) -&> (s -&> b -&> t) -&> Lens s t a b
其中(s -&> a) 可以看作一個getter, (s -&> b -&> t) 可以看作一個setter
getter描述了如何從s得到a
setter描述了如何從s減去a部分, 然後加上一個b, 得到t
比如對於s=(a, x), t=(b, x):
lens :: ((a, x) -&> a) -&> ((a, x) -&> b -&> (b, x)) -&> Lens (a, x) (b, x) a b
lens ((a, _) -&> a) ((_, x) -&> b -&> (b, _))
就重新定義了_1
得到了一個Lens之後, 就可以通過各種函數進行操作:
由於Lens既是Getter, 又是Setter, 就可以使用view函數和set函數
對於Getter, 使b = a, 就有(b, x) = (a, x)
view :: MonadReader s m =&> Getter s a -&> m a
由於(-&>)是一個MonadReader, 上面的定義可以看成
view :: Getter s a -&> s -&> a
於是對於Lens (a, x) (b, x) a b
有
view :: Lens (a, x) (a, x) a a -&> (a, x) -&> a
set :: Lens (a, x) (b, x) a b -&> (a, x) -&> b -&> (b, x)
view _1 (1, "hello") = 1
set _1 True (1, "hello") = (True, "hello")
view (_1 . _1) ((1, "hello"), "world") = 1
set (_1 . _1) True ((1, "hello"), "world") = ((True, "hello"), "world")
上面用了Lens (a, x) (b, x) a b 是故意的
是為了表現a和b的類型, s和t的類型是可以不同的
Iso是表示同構的兩種類型的互相轉化:
iso :: (s -&> a) -&> (b -&> t) -&> Iso s t a b
其中s -&> a 是getter部分, b -&> t 是setter部分
setter部分的b-&> t 是由 s -&> b -&> t變化過來的
由於s a是同構的, 所以s除去了a, 剩下部分的信息含量為0 也就是b -&> t
對於a = (a, w), b = (b, w"), s = (Writer w a), t = (Writer w" b) 有
iso (Writer w a -&> (a, w)) -&> ((b, w") -&> Writer w" b) -&> Iso (Writer w a) (Writer w" b) (a, w) (b, w")
iso runWriter Writer
Prism和Iso很像,
prism :: (b -&> t) -&> (s -&> Either s a) -&> Prism s t a b
getter部分: s -&> Either s a
setter部分: b -&> t
表示可以從b得到t
從s可能會得到a
比如, 對於s = (Either c a), t = Either c b
prism :: (b -&> Either c b) -&> (Either c a -&> Either (Either c a) a) -&> Prism (Either a c) (Either b c) a b
prism ( -&> Right b) (x -&> case x of {Right a -&> Right a; Left c -&> Left (Left c)})
==============================黑箱的分割線==============================
type Traversal s t a b = forall f. Applicative f =&> (a -&> f b) -&> s -&> f t
type Lens s t a b = forall f. Functor f =&> (a -&> f b) -&> s -&> f t
type Prism s t a b = forall p f. (Choice p, Applicative f) =&> p a (f b) -&> p s (f t)
type Iso s t a b = forall p f. (Profunctor p, Functor f) =&> p a (f b) -&> p s (f t)
(-&>) a b 是一個Profunctor, 也是Choice
對於Iso, 令p a b = a -&> b
就得到了Lens的定義
所有的Applicative 都是Functor, 所有的Choice 都是Profunctor
於是所有的Iso都是Prism, 所有的Lens都是Traversal, 所有Prism都是Traversal
Lens等類之間的組合性也可以解釋:
(.) :: ( p a (f b) -&> p s (f t) ) -&> ( p x (f y) -&> p a (f b) ) -&> ( p x (f y) -&> p s (f t) )
令 f = Identity, 有
type Setter s t a b = (a -&> Identity b) -&> s -&> Identity t
令 f = Const r, s=t, a=b
type Getting r s a = (a -&> Const r a) -&> s -&> Const r s
type Getter s a = forall r. Getting r s a
其中分別令r = a, r = [a], r = First a, r = Any, 有
view :: Getting a s a -&> s -&> a
toListOf :: Getting [a] s a -&> s -&> [a]
preview :: Getting (First a) s a -&> s -&> Maybe a
has :: Getting Any s a -&> s -&> Bool
再看Const的定義
Functor (Const m)
Monoid m =&> Applicative (Const m)
因為[a], First a, Any都是Monoid
Traversal" s a 可以是Getting [a] s a , Getting (First a) s a ,Getting Any s a
當Traversal" 滿足條件Monoid a =&> Traversal" s a 時, Traversal" s a 是Getter s a
Lens" s a 是 Getter s a
最近擼了一個erlang版的lens, 可以參考一下
slepher/lenses
參考文獻
artyom.me
此文對於lens寫的非常詳細, 就是太長了...
Lenses, Folds and Traversals
Lens 的官方文檔
推薦閱讀:
※為什麼函數式語言中all odd [] 得到True?
※C++ 用作函數式語言時,是否有語法上的缺失?
※怎樣評價 LambdaConf 提出的「函數式編程技能表」?
※Haskell Book 這本書怎麼樣?
※如何解釋 Haskell 中的單子(Monad)?