【问题标题】:Fix and Mu isomorphicFix 和 Mu 同构
【发布时间】:2020-07-19 20:17:45
【问题描述】:

recursion-schemes 包中定义了以下类型:

newtype Fix f = Fix (f (Fix f))

newtype Mu f = Mu (forall a. (f a -> a) -> a)

它们是同构的吗?如果有,如何证明?

【问题讨论】:

  • 相关:What is the difference between Fix, Mu and Nu in Ed Kmett's recursion scheme package(答案没有的一件事是明确写下的同构)。
  • 在haskell中是的(因为懒惰)在严格的语言中将是Mu f < Fix f < Nu f
  • @duplode 关于同构; Fix-to-Mu 本质上是cata,而Mu-to-Fixmu2fix (Mu x) = x Fix。棘手的部分是利用参数性证明这些是互逆的。
  • @xgrommx,在严格的上下文中,Fix 无法表示但 Mu 无法表示的术语的示例是什么? ISTM Fix 应该是最小的(直觉上是因为它是一个“数据结构”并且不能包含底部)

标签: haskell recursion-schemes fixpoint-combinators


【解决方案1】:

它们是同构的吗?

是的,它们在 Haskell 中是同构的。有关其他说明,请参阅What is the difference between Fix, Mu and Nu in Ed Kmett's recursion scheme package

如果是,你如何证明?

让我们从定义执行转换的函数开始:

muToFix :: Mu f -> Fix f
muToFix (Mu s) = s Fix

fixToMu :: Functor f => Fix f -> Mu f
fixToMu t = Mu (\alg -> cata alg t)

为了证明这些函数是同构的,我们必须证明:

muToFix . fixToMu = id
fixToMu . muToFix = id

来自Fix 并返回

同构的一个方向比另一个更直接:

muToFix (fixToMu t) = t
muToFix (fixToMu t)  -- LHS
muToFix (Mu (\f -> cata f t))
(\f -> cata f t) Fix
cata Fix t  -- See below.
t  -- LHS = RHS

上面最后一段,cata Fix t = t,可以通过cata的定义来验证:

cata :: Functor f => (f a -> a) -> Fix f -> a
cata alg = alg . fmap (cata alg) . unfix

cata Fix t 就是Fix (fmap (cata Fix) (unfix t))。我们可以使用归纳法来证明它必须是t,至少对于有限的t 而言(它在无限结构中变得更加微妙——参见本答案末尾的附录)。有两种可能性需要考虑:

  • unfix t :: f (Fix f) 是空的,没有可挖掘的递归位置。在这种情况下,对于某些z :: f Void,它必须等于fmap absurd z,因此:

    cata Fix t
    Fix (fmap (cata Fix) (unfix t))
    Fix (fmap (cata Fix) (fmap absurd z))
    Fix (fmap (cata Fix . absurd) z)
    -- fmap doesn't do anything on an empty structure.
    Fix (fmap absurd z)
    Fix (unfix t)
    t
    
  • unfix t 不为空。在这种情况下,我们至少知道fmap (cata Fix) 除了在递归位置上应用cata Fix 之外什么也做不了。这里的归纳假设是这样做会使这些位置保持不变。然后我们有:

    cata Fix t
    Fix (fmap (cata Fix) (unfix t))
    Fix (unfix t)  -- Induction hypothesis.
    t
    

(最终,cata Fix = idFix :: f (Fix f) -> Fix x 是初始 F 代数的推论。直接诉诸这一事实 在这个证明的上下文中可能是太多的捷径。)

来自Mu 并返回

给定muToFix . fixToMu = id,证明fixToMu . muToFix = id足以证明:

  • muToFix 是单射的,或者

  • fixToMu 是满射的。

让我们采用第二个选项,并查看相关定义:

newtype Mu f = Mu (forall a. (f a -> a) -> a)

fixToMu :: Functor f => Fix f -> Mu f
fixToMu t = Mu (\alg -> cata alg t)

fixToMu 是满射的,则意味着,给定任何特定的Functor f,所有forall a. (f a -> a) -> a 类型的函数都可以定义为\alg -> cata alg t,对于某些特定的t :: Fix f。然后,任务变成了对forall a. (f a -> a) -> a 函数进行编目,并查看它们是否都可以用这种形式表示。

我们如何在不依赖fixToMu 的情况下定义forall a. (f a -> a) -> a 函数?无论如何,它必须涉及使用作为参数提供的f a -> a 代数来获得a 结果。直接路线会将其应用于某些 f a 值。一个主要的警告是,由于a 是多态的,我们必须能够为a 的任何选择变出f a 的值。只要f-values 碰巧存在,这是一个可行的策略。在这种情况下,我们可以这样做:

fromEmpty :: Functor f => f Void -> forall a. (f a -> a) -> a
fromEmpty z = \alg -> alg (fmap absurd z)

为了使符号更清晰,让我们为可以用来定义forall a. (f a -> a) -> a函数的事物定义一个类型:

data Moo f = Empty (f Void)

fromMoo :: Functor f => Moo f -> forall a. (f a -> a) -> a
fromMoo (Empty z) = \alg -> alg (fmap absurd z)

除了直达路线之外,还有另一种可能性。鉴于fFunctor,如果我们以某种方式有f (Moo f) 值,我们可以应用代数两次,第一个应用程序位于外部f 层下,通过fmapfromMoo

fromLayered :: Functor f => f (Moo f) -> forall a. (f a -> a) -> a
fromLayered u = \alg -> alg (fmap (\moo -> fromMoo moo alg) u)

考虑到我们也可以从f (Moo f) 值中生成forall a. (f a -> a) -> a,因此将它们添加为Moo 的情况是有意义的:

data Moo f = Empty (f Void) | Layered (f (Moo f))

因此,fromLayered 可以并入fromMoo

fromMoo :: Functor f => Moo f -> forall a. (f a -> a) -> a
fromMoo = \case
    Empty z -> \alg -> alg (fmap absurd z)
    Layered u -> \alg -> alg (fmap (\moo -> fromMoo moo alg) u)

请注意,通过这样做,我们偷偷地从在一个f 层下应用alg 转移到在任意数量的f 层下递归应用alg

接下来,我们可以注意到f Void 值可以注入到Layered 构造函数中:

emptyLayered :: Functor f => f Void -> Moo f
emptyLayered z = Layered (fmap absurd z)

这意味着我们实际上不需要Empty 构造函数:

newtype Moo f = Moo (f (Moo f))

unMoo :: Moo f -> f (Moo f)
unMoo (Moo u) = u

fromMoo 中的Empty 情况如何?这两种情况的唯一区别是,在Empty 情况下,我们使用absurd 而不是\moo -> fromMoo moo alg。由于所有Void -> a 函数都是absurd,因此我们也不需要单独的Empty 案例:

fromMoo :: Functor f => Moo f -> forall a. (f a -> a) -> a
fromMoo (Moo u) = \alg -> alg (fmap (\moo -> fromMoo moo alg) u)

一个可能的外观调整是翻转fromMoo 参数,这样我们就不需要将fmap 的参数写成一个lambda:

foldMoo :: Functor f => (f a -> a) -> Moo f -> a
foldMoo alg (Moo u) = alg (fmap (foldMoo alg) u)

或者,更无意义:

foldMoo :: Functor f => (f a -> a) -> Moo f -> a
foldMoo alg = alg . fmap (foldMoo alg) . unMoo

此时,再次查看我们的定义表明一些重命名是有序的:

newtype Fix f = Fix (f (Fix f))

unfix :: Fix f -> f (Fix f)
unfix (Fix u) = u

cata :: Functor f => (f a -> a) -> Fix f -> a
cata alg = alg . fmap (cata alg) . unfix

fromFix :: Functor f => Fix f -> forall a. (f a -> a) -> a
fromFix t = \alg -> cata alg t

就是这样:所有forall a. (f a -> a) -> a 函数都具有\alg -> cata alg t 的形式,对于某些t :: Fix f。因此,fixToMu 是满射的,并且我们有所需的同构。

附录

在 cmets 中,就cata Fix t = t 推导中的归纳论证的适用性提出了一个密切相关的问题。至少,函子定律和参数确保fmap (cata Fix) 不会产生额外的工作(例如,它不会扩大结构,或引入额外的递归位置来挖掘),这证明了为什么要进入递归位置是推导的归纳步骤中最重要的。既然如此,如果t 是一个有限结构,那么最终将达到空f (Fix t) 的基本情况,一切都清楚了。但是,如果我们允许 t 无限大,我们可以继续无休止地下降,fmapfmap 之后 fmap 之后,而永远不会达到基本情况。

不过,无限结构的情况并不像最初看起来那么糟糕。懒惰首先使无限结构可行,它允许我们懒惰地消耗无限结构:

GHCi> :info ListF
data ListF a b = Nil | Cons a b
    -- etc.
GHCi> ones = Fix (Cons 1 ones)
GHCi> (\(Fix (Cons a _)) -> a) (cata Fix ones)
1
GHCi> (\(Fix (Cons _ (Fix (Cons a _)))) -> a) (cata Fix ones)
1

虽然递归位置的连续性无限延伸,但我们可以在任何点停止并从周围的ListF 函数上下文中获得有用的结果。需要重复的是,此类上下文不受fmap 的影响,因此我们可能使用的结构的任何有限段都将不受cata Fix 的影响。

这种惰性缓和反映了,正如本讨论中其他地方所提到的,惰性如何破坏固定点 MuFixNu 之间的区别。没有惰性,Fix 不足以编码高效的核心递归,因此我们必须切换到最大不动点Nu。这是差异的一个小例子:

GHCi> :set -XBangPatterns
GHCi> -- Like ListF, but strict in the recursive position.
GHCi> data SListF a b = SNil | SCons a !b deriving Functor
GHCi> ones = Nu (\() -> SCons 1 ()) ()
GHCi> (\(Nu c a) -> (\(SCons a _) -> a) (c a)) ones
1
GHCi> ones' = Fix (SCons 1 ones')
GHCi> (\(Fix (SCons a _)) -> a) ones'
^CInterrupted.

【讨论】:

  • 你如何证明cata Fix t = t的合理性?假设Fix ff 的初始代数似乎有点捷径。 (从相关答案链接的证明似乎通过两种方式使用参数来绕过这一点。)
  • 我不明白fixToMu的满射性的证明。 “如果我们想定义一个 forall a. (f a -> a) -> 从头开始​​的函数” 这不是我们想要的。相反,让k :: forall a. (f a -> a) -> a,我们需要为一些t 显示k = \alg -> cata alg t
  • [1/2] @Li-yaoXia (1) 在cata Fix,我们有cata Fix = Fix . fmap (cata Fix) . unfix。如果t 没有递归位置,fmap (cata Fix) 将什么也不做,所以cata Fix t = Fix (unfix t) = t。如果它有递归位置,所有fmap (cata Fix) 将做的只是将cata Fix 应用于它们,这看起来足以通过归纳来解决问题。
  • [2/2] @Li-yaoXia (2) 关于满射性:论据是任何可能的k 必须通过直接应用代数获得(其中f Void 值为需要)或使用fmap 递归地应用它,两种情况都可以用 \alg -> cata alg t` 形式表示。所以我相信我已经按照你的建议做了,尽管“从头开始”可能不是描述它的最佳选择。
  • @Li-yaoXia 我已经调整了描述满射性论点的语言,并添加了cata Fix t = t 推导(事实上,鉴于这两个论点之间的相似之处,我现在觉得将其布局有助于为答案的第二部分准备基础)。感谢您强调这些需要改进的地方。
猜你喜欢
  • 2014-01-20
  • 2018-01-16
  • 1970-01-01
  • 2020-05-14
  • 1970-01-01
  • 2021-03-14
  • 1970-01-01
  • 2020-02-10
  • 2015-08-16
相关资源
最近更新 更多