標籤:

如何理清 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

lens 可以有好幾種實現方法,簡單的是:

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)?

TAG:Haskell | 範疇論 |