Haskell 箭头定律:第一个仅取决于对的第一个分量。我们为什么需要这个?
约翰·休斯(John Hughes)在其《将单子概括为箭头》(第8章)中写道: 我们将Haskell 箭头定律:第一个仅取决于对的第一个分量。我们为什么需要这个?,haskell,monads,arrows,combinators,kleisli,Haskell,Monads,Arrows,Combinators,Kleisli,约翰·休斯(John Hughes)在其《将单子概括为箭头》(第8章)中写道: 我们将第一个f仅依赖于对的第一个分量的属性形式化,如下所示: first f>>>arr fst=arr fst>>>f 我知道法律过滤掉了这类实施: newtype KleisliMaybe a b = KMb { runKMb :: a -> Maybe b } instance Category KleisliMaybe where ... instance Arrow KleisliMaybe
第一个f
仅依赖于对的第一个分量的属性形式化,如下所示:
first f>>>arr fst=arr fst>>>f
我知道法律过滤掉了这类实施:
newtype KleisliMaybe a b = KMb { runKMb :: a -> Maybe b }
instance Category KleisliMaybe where
...
instance Arrow KleisliMaybe where
first f = KMb $ const Nothing
...
但在这种情况下,措辞似乎有点奇怪(我会选择“首先
没有副作用”或类似的例子)
那么,还有什么其他理由让它继续存在呢
此外,还有另一条定律:first f>>>second(arrg)=second(arrg)>>>first f
。我没有找到它过滤掉的任何实现(我找到了-请参阅编辑)。这项法律对我们有什么帮助
编辑:关于后一条法则的更多想法。 请看以下代码段:
newtype KleisliWriter = KW { runKW :: a -> (String, b) }
instance Category KleisliWriter where
...
instance Arrow KleisliWriter where
arr f = KW $ \ x -> ("", f x)
first (KW f) = KW $ \ ~(a, d) -> f a >>= (\ x -> ("A", (x, d)))
second (KW f) = KW $ \ ~(d, b) -> f b >>= (\ x -> ("B", (d, x)))
此类实例的行为方式如下:
GHCi> c = KW $ \ x -> ("C", x)
GHCi> fst . runKW (first c >>> second (arr id)) $ (1, 2)
"CAB"
GHCi> fst . runKW (second (arr id) >>> first c) $ (1, 2)
"BCA"
就我所知,我们没有关于第二个f=swap>>第一个f>>swap的法律。因此,我们可以禁止second
和first
对该法律产生任何副作用。然而,最初的措辞似乎仍然没有再次暗示:
…我们将成对的第二个分量不受第一个f影响的直觉形式化为一条定律
这些定律仅仅是可靠证明的形式化吗?简短回答:有一对不同的定律,涵盖了“
第一个和第二个
没有副作用”:
思考之后,我认为你已经确定的两条定律:
first f >>> arr fst = arr fst >>> f -- LAW-A
first f >>> second (arr g) = second (arr g) >>> first f -- LAW-B
事实上,它们是多余的,因为它们遵循那些没有副作用的定律、其他定律和一些“自由定理”
你的反例违反了无副作用法,所以这就是为什么它们也违反了A法和/或B法。如果有人有一个真正的反例,它遵守了无副作用法,但违反了A法或B法,我会非常有兴趣看到它
长答案:
财产“首先
没有副作用(至少它本身)”最好由该条第8节前面所述的法律形式化:
first (arr f) = arr (first f)
回想一下,休斯曾说过,如果一支箭可以写成arr expr
,它就是“纯的”(相当于“没有副作用”)。因此,该定律规定,如果任何计算已经是纯的,因此可以写入arrf
,那么将first
应用于该计算也会导致纯计算(因为它的形式是arrfpr
和expr=first f
)。因此,首先
不会引入杂质/自身不会产生影响
其他两条法律:
first f >>> arr fst = arr fst >>> f -- LAW-A
first f >>> second (arr g) = second (arr g) >>> first f -- LAW-B
旨在捕捉以下思想:对于特定的实例Arrow Foo
和特定的Arrow actionf::Foo B C
,操作:
first f :: forall d. Foo (B,d) (C,d)
arr (\(x,y) -> (x, g y)) >>> (first f >>> arr fst) = first f >>> arr fst
作用于其输入/输出对的第一个组件,就像第二个组件不存在一样。法律对应于以下属性:
法则A:输出成分C
和任何副作用仅取决于输入B
,而非输入d
(即,不依赖于d
)
定律B:组件d
未受输入B
或任何副作用的影响(即对d
无影响),未发生变化
关于法-A,如果考虑动作<代码> F::FoO(b,d)(c,d)< /代码>,并使用纯函数将其输出的代码C>/C>组件集中到:
first f >>> arr fst :: Foo (B,d) C
然后,结果与我们首先使用纯动作强制移除第二个组件的结果相同:
arr fst :: Foo (B,d) B
并允许原始操作f
仅作用于B
:
arr fst >>> f :: Foo (B,d) C
这里,first f>>>arr fst
动作的结构使得first f
可能依赖于输入的d
分量来制定其副作用并构建其输出的C
分量;但是,arr fst>>f
操作的结构通过在允许通过f
进行任何非平凡计算之前通过纯操作移除d
组件,消除了这种可能性。这两个动作相等的事实(定律)清楚地表明,first f
从B
输入中产生C
输出(以及通过f
产生的副作用,因为first
本身没有额外的效果),其方式也不能依赖于d
输入
B定律更难。将该属性形式化的最明显方式是伪法律:
first f >>> arr snd = arr snd
这直接表明第一个f
不会改变提取的(arr snd
)第二个分量。然而,休斯指出,这是限制性的,因为它不允许第一个f
产生副作用(或至少任何可以在纯动作arr snd
中存活的副作用)。相反,他提供了更复杂的法律:
first f >>> second (arr g) = second (arr g) >>> first f
这里的想法是,如果first f
修改了d
值,那么在某些情况下,以下两个操作将不同:
-- `first f` changes `inval` to something else
second (arr (const inval)) >>> first f
-- if we change it back, we change the action
second (arr (const inval)) >>> first f >>> second (arr (const inval))
但是,由于B法,我们有:
second (arr (const inval)) >>> first f >>> second (arr (const inval))
-- associativity
= second (arr (const inval)) >>> (first f >>> second (arr (const inval)))
-- LAW-B
= second (arr (const inval)) >>> (second (arr (const inval)) >>> first f)
-- associativity
= (second (arr (const inval)) >>> (second (arr (const inval))) >>> first f
-- second and arr preserve composition
= second (arr (const inval >>> const inval)) >>> first f
-- properties of const function
= second (arr (const inval)) >>> first f
因此,这些行为是相同的,与我们的假设相反
然而,我猜想A定律和B定律都是多余的,因为我相信(见下面我的犹豫)它们遵循其他定律加上签名的“自由定理”:
first f :: forall d. Foo (B,d) (C,d)
假设first
和second
满足无副作用定律:
first (arr f) = arr (first f)
second (arr f) = arr (second f)
那么定律B可以改写为:
first f >>> second (arr g) = second (arr g) >>> first f
-- no side effects for "second"
first f >>> arr (second g) = arr (second g) >>> first f
-- definition of "second" for functions
= first f >>> arr (\(x,y) -> (x, g y)) = arr (\(x,y) -> (x, g y)) >>> first f
最后这句话就是第一个f的自由定理。(直观地说,由于first f
在d
类型中是多态的,因此d
上的任何纯动作都必然对first f
是“不可见的”,因此
first f >>> arr fst :: forall d. Foo (B,d) C
arr (\(x,y) -> (x, g y)) >>> (first f >>> arr fst) = first f >>> arr fst
-- by associativity
(arr (\(x,y) -> (x, g y)) >>> first f) >>> arr fst
-- by rewritten version of LAW-B
(first f >>> arr (\(x,y) -> (x, g y))) >>> arr fst
-- by associativity
first f >>> (arr (\(x,y) -> (x, g y)) >>> arr fst)
-- `arr` preserves composition
first f >>> arr ((\(x,y) -> (x, g y)) >>> fst)
-- properties of fst
first f >>> arr fst
> runKMb (first (arr (2*))) (2,3)
Nothing
> runKMb (arr (first (2*))) (2,3)
Just (4,3)
> runKW (first (arr (2*))) (1,2)
("A",(2,2))
> runKW (arr (first (2*))) (1,2)
("",(2,2))