isomorphism lens 是 Twan van Laarhoven 继 「Van Laarhoven Lens」后发明的一种新的 lens 实现。
这种实现更加简洁、更符合直觉,所以易于理解,在部分情况下比前者更加高效,但大多数场景下可能没有前者使用方便。
定义
isomorphism lens 的实现思想十分富有创意,他将 lens 抽象定义为:
A lens from type a to b is a bijection between a and a pair of b and some residual r.
翻译过来就是: 一个 a 到 b 的 lens 是 a 到 (b, r) 的双射,其中 r 为 a 中除去 b 后的剩余部分
简单解释下:
-
设类型 a 为一个集合,集合内各个元素为其字段
-
b 为 a 中的一个字段
-
则 r = a - {b}
-
lens 为一个双射/同构, 由两个互逆函数 f 和 b 组成
f : a -> (b, r)
b : (b, r) -> a
因此这种 lens 被称作同构 lens
作者为了便于理解,特意制作了一副示意图:
结合图片,就可以十分简单明了的重新定义访问和更新了
- 访问
- 将元素 b 从集合 a 中拿出来,得到 b 和 余集 r
- 更新
- 将更新后的元素 b 加入到余集 r 中,就得到更新后的新集合 a
在代码中的定义
type ISO<'a,'b> = ('a -> 'b) * ('b -> 'a)
type Lens<'a, 'b, 'r> = Lens of ISO<'a, 'b * 'r>
get/set 的实现
get
let get (Lens iso) = fst iso >> fst
get 的实现比较简单,取出 ISO 结构中的 a -> (b, r) 然后 和 fst 函数复合就可以了
更加详细易于理解的代码如下:
let get (Lens iso) =
let f = fst iso // 取出元组中的 a -> (b, r) 部分,绑定到变量 f 上
fun a -> // 返回 getter 函数, getter : a -> b
let pair = f a // 将 a 应用到函数 f 上
fst pair // 从 (b, r) 中取出 b
set 的实现
let first f (a, b) =
(f a, b)
let private mkConst a b = a
let modify (Lens iso) f = fst iso >> first f >> snd iso
let put l a = modify l (mkConst a)
set 的实现是先取出 (b, r),然后将更新函数 f 应用到 b 上,最后将得到的结果应用到 ISO 的 (b, r) -> a 上
同上,详细的代码如下:
let moify (Lens iso) f =
let f1 = fst iso // 取出 a -> (b, r) 部分
let f2 = // 复合更新函数 f,将结果绑定到变量 f2 上, f2 : a -> (b, r)
fun a ->
let (b, r) = f1 a
(f b, r)
let w = snd iso // 取出更新函数 w : (b, r) -> a
fun a -> // 返回 setter : b -> a
let res = f2 a // 将 a 作用到 f2 上,实现对 b 的更新
w res // 将 res : (b, r) 应用到 w 上,得到更新后的 a
示例
示例和上次一样,演示对深层嵌套结构的更新,在这种情况下 isomorphism lens 在自由组合上,不如 van laarhoven lens 方便
open Lens
// 定义需要更新的数据类型
type Skill = {
Damage : int
}
type Monster = {
Name : string
Skill : Skill
}
// 定义 Monster -> Skill 的 a -> (b, r) 部分
let fwm m = (m.Skill, fun s -> {m with Skill = s})
// 定义 Skill -> Damage 的 a -> (b, r) 部分
let fws s = (s.Damage, fun d -> {s with Damage = d})
// 在 record 的更新这种情况下, (b, r) -> a 是一个通用的函数
let bw (l, r) = r l
// lens 的复合
// 可以看见,在处理 record 这种情况下
// isomorphism lens 的复合 比 van laarhoven lens 麻烦些
// 但是比朴素的 (getter, setter) 元组又要简便许多
// 在处理 tuple 或者数据本身就存在同构时, isomorphism lens 的复合则会十分简单
let com f1 f2 s =
let (s2, r1) = f1 s
let (s3, r2) = f2 s2
(s3, r2 >> r1)
// 得到 Monster -> Damage 的 lens
let lens = Lens (com fwm fws, bw)
let monster = {Name = "A"; Skill = {Damage = 12}}
// 访问
get lens monster |> printfn "Damage is:%O"
// 更新
put lens 33 monster |> printfn "New Monster is:%O"
结果:
Damage is:12
New Monster is:{ Name = "A"
Skill = { Damage = 33 } }