应用函子的契约
就像 Functor、Monad 以及实现了 BEq 和 Hashable 的类型一样,Applicative 也有一套所有实例都应遵守的规则。
应用函子应该遵循四条规则:
- 应遵循同一律,即
pure id <*> v = v - 应遵循函数复合律,即
pure (· ∘ ·) <*> u <*> v <*> w = u <*> (v <*> w) - 对纯操作进行排序应等同于无操作,即
pure f <*> pure x = pure (f x) - 纯操作的顺序不应影响结果,即
u <*> pure x = pure (fun f => f x) <*> u
要检验对于 Applicative Option 实例的这些规则,首先将 pure 展开为 some。
第一条规则表明 some id <*> v = v。
Option 的 seq 定义指明,这与 id <$> v = v 相同,这是已被检查过的 Functor 规则之一。
第二条规则指出 some (· ∘ ·) <*> u <*> v <*> w = u <*> (v <*> w)。
如果 u、v 或 w 中有任何一个是 none,则两边均为 none,因此该属性成立。
假设 u 是 some f,v 是 some g,w 是 some x,那么这等价于声明 some (· ∘ ·) <*> some f <*> some g <*> some x = some f <*> (some g <*> some x)。
对两边求值得到相同的结果:
some (· ∘ ·) <*> some f <*> some g <*> some x
===>
some (f ∘ ·) <*> some g <*> some x
===>
some (f ∘ g) <*> some x
===>
some ((f ∘ g) x)
===>
some (f (g x))
some f <*> (some g <*> some x)
===>
some f <*> (some (g x))
===>
some (f (g x))
第三条规则直接源于 seq 的定义:
some f <*> some x
===>
f <$> some x
===>
some (f x)
在第四种情况下,假设 u 是 some f,因为如果它是 none,则等式的两边都是 none。
some f <*> some x 的求值结果直接为 some (f x),正如 some (fun g => g x) <*> some f 也是如此。
所有的应用函子都是函子
Applicative 的两个运算符足以定义 map:
def map [Applicative f] (g : α → β) (x : f α) : f β :=
pure g <*> x
但是,只有当 Applicative 的契约保证了 Functor 的契约时,这才能用来实现 Functor。
Functor 的第一条规则是 id <$> x = x,这直接源于 Applicative 的第一条规则。
Functor 的第二条规则是 map (f ∘ g) x = map f (map g x)。
在这里展开 map 的定义会得到 pure (f ∘ g) <*> x = pure f <*> (pure g <*> x)。
使用将纯操作进行排序视为无操作的规则,其左侧可以重写为 pure (· ∘ ·) <*> pure f <*> pure g <*> x。
这是应用函子遵守函数复合规则的一个实例。
这证明了定义一个扩展自 Functor 的 Applicative 是合理的,其中 map 的默认定义可以用 pure 和 seq 来表示:
class Applicative (f : Type → Type) extends Functor f where
pure : α → f α
seq : f (α → β) → (Unit → f α) → f β
map g x := seq (pure g) (fun () => x)
所有单子都是应用函子
Monad 的一个实例已经需要实现 pure。
结合 bind,这足以定义 seq:
def seq [Monad m] (f : m (α → β)) (x : Unit → m α) : m β := do
let g ← f
let y ← x ()
pure (g y)
再一次,检验 Monad 的契约暗含 Applicative 的契约,如果 Monad 扩展自 Applicative,这将允许将其用作 seq 的默认定义。
本节的其余部分包含一个论点,即基于 bind 的 seq 的这种实现实际上满足了 Applicative 契约。
函数式编程的一个美妙之处在于,这种论点可以用铅笔在纸上完成,使用表达式求值初步小节中的求值规则。
在阅读这些论点时,思考操作的含义有时有助于理解。
将 do 表示法替换为 >>= 的显式使用可以更容易地应用 Monad 规则:
def seq [Monad m] (f : m (α → β)) (x : Unit → m α) : m β := do
f >>= fun g =>
x () >>= fun y =>
pure (g y)
为了检查这个定义遵循恒等性,请检验 seq (pure id) (fun () => v) = v。
左侧等价于 pure id >>= fun g => (fun () => v) () >>= fun y => pure (g y)。
中间的单位函数可以立即消除,得到 pure id >>= fun g => v >>= fun y => pure (g y)。
利用 pure 是 >>= 的左恒等性这一事实,其等同于 v >>= fun y => pure (id y),也就是 v >>= fun y => pure y。
因为 fun x => f x 与 f 是相同的,所以这与 v >>= pure 相同,并且 pure 是 >>= 的右恒等性可以被用来获取 v。
这种非正式的推理可以通过稍微的重新编排来使其更易阅读。 在下表中,读取 "EXPR1 ={ REASON }= EXPR2" 时,请将其理解为 "EXPR1 因为 REASON 与 EXPR2 相同":
pure id >>= fun g => v >>= fun y => pure (g y)
pure is a left identity of >>= }=
v >>= fun y => pure (id y)
id }=
v >>= fun y => pure y
fun x => f x is the same as f }=
v >>= pure
pure is a right identity of >>= }=
v
要检查它遵守函数复合的规则,请验证 pure (· ∘ ·) <*> u <*> v <*> w = u <*> (v <*> w)。
第一步是用 seq 的这个定义替换 <*>。
之后,使用 Monad 契约中的恒等律和结合律规则的一系列(有点长的)步骤,以从一个得到另一个:
seq (seq (seq (pure (· ∘ ·)) (fun _ => u))
(fun _ => v))
(fun _ => w)
seq }=
((pure (· ∘ ·) >>= fun f =>
u >>= fun x =>
pure (f x)) >>= fun g =>
v >>= fun y =>
pure (g y)) >>= fun h =>
w >>= fun z =>
pure (h z)
pure is a left identity of >>= }=
((u >>= fun x =>
pure (x ∘ ·)) >>= fun g =>
v >>= fun y =>
pure (g y)) >>= fun h =>
w >>= fun z =>
pure (h z)
((u >>= fun x =>
pure (x ∘ ·)) >>= (fun g =>
v >>= fun y =>
pure (g y))) >>= fun h =>
w >>= fun z =>
pure (h z)
>>= }=
(u >>= fun x =>
pure (x ∘ ·) >>= fun g =>
v >>= fun y => pure (g y)) >>= fun h =>
w >>= fun z =>
pure (h z)
pure is a left identity of >>= }=
(u >>= fun x =>
v >>= fun y =>
pure (x ∘ y)) >>= fun h =>
w >>= fun z =>
pure (h z)
>>= }=
u >>= fun x =>
v >>= fun y =>
pure (x ∘ y) >>= fun h =>
w >>= fun z =>
pure (h z)
pure is a left identity of >>= }=
u >>= fun x =>
v >>= fun y =>
w >>= fun z =>
pure ((x ∘ y) z)
u >>= fun x =>
v >>= fun y =>
w >>= fun z =>
pure (x (y z))
pure is a left identity of >>= }=
u >>= fun x =>
v >>= fun y =>
w >>= fun z =>
pure (y z) >>= fun q =>
pure (x q)
>>= }=
u >>= fun x =>
v >>= fun y =>
(w >>= fun p =>
pure (y p)) >>= fun q =>
pure (x q)
>>= }=
u >>= fun x =>
(v >>= fun y =>
w >>= fun q =>
pure (y q)) >>= fun z =>
pure (x z)
seq }=
u >>= fun x =>
seq v (fun () => w) >>= fun q =>
pure (x q)
seq }=
seq u (fun () => seq v (fun () => w))
以检验对纯操作进行排序为无操作:
seq (pure f) (fun () => pure x)
seq with its definition }=
pure f >>= fun g =>
pure x >>= fun y =>
pure (g y)
pure is a left identity of >>= }=
pure f >>= fun g =>
pure (g x)
pure is a left identity of >>= }=
pure (f x)
最后,以检验纯操作的顺序是无关紧要的:
seq u (fun () => pure x)
seq }=
u >>= fun f =>
pure x >>= fun y =>
pure (f y)
pure is a left identity of >>= }=
u >>= fun f =>
pure (f x)
u >>= fun f =>
pure ((fun g => g x) f)
pure is a left identity of >>= }=
pure (fun g => g x) >>= fun h =>
u >>= fun f =>
pure (h f)
seq }=
seq (pure (fun f => f x)) (fun () => u)
这证明了 Monad 的定义扩展自 Applicative,并提供一个 seq 的默认定义:
class Monad (m : Type → Type) extends Applicative m where
bind : m α → (α → m β) → m β
seq f x :=
bind f fun g =>
bind (x ()) fun y =>
pure (g y)
Applicative 自身对 map 的默认定义意味着每个 Monad 实例都会自动生成 Applicative 和 Functor 实例。
附加规定
除了遵守每个类型类相关的单独契约之外,Functor、Applicative 和 Monad 的组合实现应与这些默认实现等效。
换句话说,一个同时提供 Applicative 和 Monad 实例的类型,其 seq 的实现不应与 Monad 实例生成的默认实现不同。
这很重要,因为多态函数可以被重构,以使用 <*> 的等效使用去替换 >>=,或者用 >>= 的等效使用去替换 <*>。
这种重构不应改变使用该代码的程序的含义。
这条规则解释了为什么在 Monad 实例中不应使用 Validate.andThen 来实现 bind。
就其本身而言,它遵守单子契约。
然而,当它用于实现 seq 时,其行为并不等同于 seq 本身。
要了解它们的区别,举一个两个计算都返回错误的例子。
首先来看一个应该返回两个错误的情况,一个来自函数验证(这也可能是由函数先前的一个参数导致的),另一个来自参数验证:
def notFun : Validate String (Nat → String) :=
.errors { head := "First error", tail := [] }
def notArg : Validate String Nat :=
.errors { head := "Second error", tail := [] }
将它们与 Validate 的 Applicative 实例中的 <*> 版本结合起来,会导致两个错误都被报告给用户:
notFun <*> notArg
===>
match notFun with
| .ok g => g <$> notArg
| .errors errs =>
match notArg with
| .ok _ => .errors errs
| .errors errs' => .errors (errs ++ errs')
===>
match notArg with
| .ok _ => .errors { head := "First error", tail := [] }
| .errors errs' => .errors ({ head := "First error", tail := [] } ++ errs')
===>
.errors ({ head := "First error", tail := [] } ++ { head := "Second error", tail := []})
===>
.errors { head := "First error", tail := ["Second error"]}
使用由 >>= 实现的 seq 版本(这里重写为 andThen)会导致只出现第一个错误:
seq notFun (fun () => notArg)
===>
notFun.andThen fun g =>
notArg.andThen fun y =>
pure (g y)
===>
match notFun with
| .errors errs => .errors errs
| .ok val =>
(fun g =>
notArg.andThen fun y =>
pure (g y)) val
===>
.errors { head := "First error", tail := [] }