![](/img/trans.png)
[英]What's the most efficient way to represent finite (non-recursive) algebraic type values?
[英]What's the type of a catamorphism (fold) for non-regular recursive types?
許多catamorphisms似乎很簡單,大多數用自定義函數替換每個數據構造函數,例如
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
,而是將其傳遞給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
我有兩個看似合理的想法???
部分是
(a -> b -> b)
,即遞歸替換List
構造函數的所有應用程序) (a -> List b -> b)
,即僅替換所有List a
應用程序。 哪兩個是正確的 - 為什么? 或者它會完全不同嗎?
這只是部分答案。
OP提出的問題是:如何在非常規遞歸類型的情況下定義fold
/ cata
?
由於我不相信自己這么做,我會求助於Coq。 讓我們從一個簡單的,常規的遞歸列表類型開始。
Inductive List (A : Type) : Type :=
| Empty: List A
| Cons : A -> List 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 : Type -> Type
of List2
。 這一點的主要觀點是對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
參數,但如果需要,我們可以忽略它。
現在,上面仍然是常規類型的折疊/ 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-algebras ... ;-)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.