【问题标题】:What's the type of a catamorphism (fold) for non-regular recursive types?非常规递归类型的变态(折叠)类型是什么?
【发布时间】:2015-08-28 21:04:27
【问题描述】:

许多变态似乎很简单,主要是用自定义函数替换每个数据构造函数,例如

data Bool = False | True
foldBool :: r              -- False constructor
         -> r              -- True constructor
         -> Bool -> r

data Maybe a = Nothing | Just a
foldMaybe :: b             -- Nothing constructor
          -> (a -> b)      -- Just constructor
          -> Maybe a -> b

data List a = Empty | Cons a (List a)
foldList :: b              -- Empty constructor
         -> (a -> b -> b)  -- Cons constructor
         -> List a -> b

但是,我不清楚的是,如果使用相同类型的构造函数,但使用不同的类型参数会发生什么。例如。而不是将List a 传递给Cons,那么

data List a = Empty | Cons a (List (a,a))

或者,也许是一个更疯狂的案例:

data List a = Empty | Cons a (List (List a))
foldList :: b              -- Empty constructor
         -> ???            -- Cons constructor
         -> List a -> b

我对@9​​87654327@ 部分的两个似是而非的想法是

  • (a -> b -> b),即递归替换List构造函数的所有应用)
  • (a -> List b -> b),即仅替换所有 List a 应用程序。

两者中哪一个是正确的 - 为什么?还是会完全不同?

【问题讨论】:

  • 多态递归绝对是不平凡的。 data List a = Empty | Cons a (List (a,a))
  • @chi 谢谢!我认为non-regular recursive types 是我应该提到的一个术语——我调整了我的问题的标题——我从不喜欢this type 部分。我还在我的问题中加入了你不太极端的例子。
  • 在我的理解中,折叠只为那些作为内函子 F 的初始代数出现的递归数据类型定义。我怀疑每个递归数据类型都是这种形式,因此每个数据类型都应该有一个fold....反正我不确定你的数据类型是否允许折叠。

标签: haskell types catamorphism recursion-schemes


【解决方案1】:

这只是部分答案。

OP提出的问题是:在非正则递归类型的情况下如何定义fold/cata

由于我不相信自己能做到这一点,我将求助于 Coq。让我们从一个简单的常规递归列表类型开始。

Inductive List (A : Type) : Type :=
  | Empty: List A
  | Cons : A -> List A -> List A
.

这里没什么特别的,List A 是根据List A 定义的。 (记住这一点——我们会回到它。)

cata 呢?让我们查询归纳原理。

> Check List_rect.
forall (A : Type) (P : List A -> Type),
   P (Empty A) ->
   (forall (a : A) (l : List A), P l -> P (Cons A a l)) ->
   forall l : List A, P l

让我们看看。以上利用依赖类型:P 依赖于列表的实际值。在P list 是常量类型B 的情况下,让我们手动简化它。我们得到:

forall (A : Type) (B : Type),
   B ->
   (forall (a : A) (l : List A), B -> B) ->
   forall l : List A, B

可以等价写成

forall (A : Type) (B : Type),
   B ->
   (A -> List A -> B -> B) ->
   List A -> B

这是foldr,除了“当前列表”也被传递给二进制函数参数——没有主要区别。

现在,在 Coq 中,我们可以用另一种稍微不同的方式定义列表:

Inductive List2 : Type -> Type :=
  | Empty2: forall A, List2 A
  | Cons2 : forall A, A -> List2 A -> List2 A
.

它看起来是同一类型,但有很大的不同。这里我们没有根据List A 定义类型List A。相反,我们是根据List2 定义类型函数List2 : Type -> Type。这样做的要点是,对List2 的递归引用不必应用于A——事实上我们这样做只是一个事件。

不管怎样,让我们​​看看归纳原理的类型:

> Check List2_rect.
forall P : forall T : Type, List2 T -> Type,
   (forall A : Type, P A (Empty2 A)) ->
   (forall (A : Type) (a : A) (l : List2 A), P A l -> P A (Cons2 A a l)) ->
   forall (T : Type) (l : List2 T), P T l

让我们像以前一样从P 中删除List2 T 参数,基本上假设P 是不变的。

forall P : forall T : Type, Type,
   (forall A : Type, P A ) ->
   (forall (A : Type) (a : A) (l : List2 A), P A -> P A) ->
   forall (T : Type) (l : List2 T), P T

等效改写:

forall P : (Type -> Type),
   (forall A : Type, P A) ->
   (forall (A : Type), A -> List2 A -> P A -> P A) ->
   forall (T : Type), List2 T -> P T

大致对应,用 Haskell 表示法

(forall a, p a) ->                          -- Empty
(forall a, a -> List2 a -> p a -> p a) ->   -- Cons
List2 t -> p t

还不错——基本情况现在必须是一个多态函数,就像 Haskell 中的 Empty 一样。这有点道理。类似地,归纳案例必须是多态函数,就像Cons 一样。还有一个额外的 List2 a 参数,但如果我们愿意,我们可以忽略它。

现在,上面仍然是 regular 类型上的一种折叠/cata。非正规的呢?我会学习

data List a = Empty | Cons a (List (a,a))

在 Coq 中变成:

Inductive  List3 : Type -> Type :=
  | Empty3: forall A, List3 A
  | Cons3 : forall A, A -> List3 (A * A) -> List3 A
.

用归纳原理:

> Check List3_rect.
forall P : forall T : Type, List3 T -> Type,
   (forall A : Type, P A (Empty3 A)) ->
   (forall (A : Type) (a : A) (l : List3 (A * A)), P (A * A) l -> P A (Cons3 A a l)) ->
   forall (T : Type) (l : List3 T), P T l

删除“依赖”部分:

forall P : (Type -> Type),
   (forall A : Type, P A) ->
   (forall (A : Type), A -> List3 (A * A) -> P (A * A) -> P A ) ->
   forall (T : Type), List3 T -> P T

在 Haskell 表示法中:

   (forall a. p a) ->                                      -- empty
   (forall a, a -> List3 (a, a) -> p (a, a) -> p a ) ->    -- cons
   List3 t -> p t

除了额外的List3 (a, a) 参数,这是一种折叠。

最后,OP类型呢?

data List a = Empty | Cons a (List (List a))

唉,Coq 不接受该类型

Inductive  List4 : Type -> Type :=
  | Empty4: forall A, List4 A
  | Cons4 : forall A, A -> List4 (List4 A) -> List4 A
.

因为内部的List4 出现不在严格的正数位置。这可能是一个暗示,我应该停止偷懒并使用 Coq 来完成工作,并开始自己思考所涉及的 F 代数...... ;-)

【讨论】:

    猜你喜欢
    • 2013-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-09
    • 1970-01-01
    • 1970-01-01
    • 2018-08-04
    • 2022-02-07
    相关资源
    最近更新 更多