有没有一种方法可以对Coq的类型类使用递归?例如,在定义列表显示时,如果要递归调用列表的show
函数,则必须使用固定点,如下所示:
Require Import Strings.String.
Require Import Strings.Ascii.
Local Open Scope string_scope.
Class Show (A : Type) : Type :=
{
show : A -> string
}.
Section showNormal.
Instance showList {A : Type} `{Show A} : Show (list A) :=
{
show :=
fix lshow l :=
match l with
| nil => "[]"
| x :: xs => show x ++ " : " ++ lshow xs
end
}.
End showNormal.
哪个都很好,但是如果我想定义一些用于定义Show
实例的辅助函数,该怎么办?就像我想创建一个名为magicShow
的更加令人眼花show乱的展示功能,该功能可以在某物周围打印星星...
Definition magicShow {A : Type} `{Show A} (a : A) : string :=
"** " ++ show a ++ " **".
Instance showMagicList {A : Type} `{Show A} : Show (list A) :=
{
show :=
fix lshow l :=
match l with
| nil => "[]"
| x :: xs => show x ++ " : " ++ magicShow xs
end
}.
但是,在这种情况下,Coq找不到列表xs
传递给magicShow
的表演实例:
Error:
Unable to satisfy the following constraints:
In environment:
A : Type
H : Show A
lshow : list A -> string
l : list A
x : A
xs : list A
?H : "Show (list A)"
一般有什么办法吗?即,您可以使用依赖于您所定义的类型类实例的函数来为类型类定义方法吗?
答案 0 :(得分:5)
不,没有办法做到这一点。这在Haskell中有效,因为允许任意递归绑定,并且该语言不关心绑定的顺序。 Coq在这两个方面都更具限制性。如果您考虑一下废止的样子,这是有道理的:对show
的递归调用将按名称引用当前正在定义的实例,但是该绑定尚不在范围内。而且,您不能将实例本身作为一个固定点,因为您要递归类型的结构,而不是代数数据类型的值。
您的内联固定点可用于show
,但是如果您的方法实现相互引用,则问题变得更加棘手,例如
newtype MyInteger = MyInteger Integer
instance Num MyInteger where
MyInteger m + MyInteger n = MyInteger $ m + n
negate (MyInteger m) = MyInteger $ negate m
m - n = m + negate n
-- other methods
在这里,对(+)
的定义中的negate
和(-)
的调用需要引用上面的(+)
和negate
的定义,但这在Coq中也不起作用。唯一的解决方案是分别定义所有方法,手动相互引用,然后仅通过将每种方法设置为上面定义的方法来定义实例。例如,
Inductive MyInteger := Mk_MyInteger : Integer -> MyInteger.
Definition add__MyInteger (m : MyInteger) : MyInteger :=
let 'Mk_MyInteger m' := m in
let 'Mk_MyInteger n' := n in
Mk_MyInteger (add m' n').
Definition negate__MyInteger (m : MyInteger) : MyInteger :=
let 'Mk_MyInteger m' := m in
Mk_MyInteger (negate m').
Definition sub__MyInteger (m n : MyInteger) : MyInteger :=
add__MyInteger m + negate__MyInteger n.
Instance Num__MyInteger : Num MyInteger := {|
add := add__MyInteger;
negate := negate__MyInteger;
sub := sub__MyInteger;
(* other methods *)
|}
答案 1 :(得分:1)
如果必须执行此操作,则可以通过显式使用基础Record
的构造函数进行模拟(因为“ Typeclasses are Records”,引自软件基金会[1]),可以使用定义为固定点的函数实例化。我将发布三个示例,并说明在哪些地方有用。
您发布的示例可以像这样解决(针对Coq 8.10.1测试的所有代码):
Require Import Strings.String.
Local Open Scope list_scope.
Local Open Scope string_scope.
Class Show (A : Type) : Type :=
{
show : A -> string
}.
Definition magicShow {A : Type} `{Show A} (a : A) : string :=
"** " ++ show a ++ " **".
Print Show.
(* Record Show (A : Type) : Type := Build_Show { show : A -> string }
*)
Check Build_Show.
(* Build_Show : forall A : Type, (A -> string) -> Show A *)
Check @magicShow.
(* @magicShow : forall A : Type, Show A -> A -> string *)
Instance showMagicList {A : Type} `{Show A} : Show (list A) :=
{
show :=
fix lshow l :=
match l with
| nil => "[]"
| x :: xs => show x ++ " : " ++ @magicShow _ (@Build_Show _ lshow) xs
end
}.
如果您试图定义这样的几个类型类方法,则实例化记录构造函数很棘手,但是可以通过将函数视为由相互递归来定义来完成(尽管不一定必须任何实际的相互递归)。这是一个人为的示例,其中Show
现在有两种方法。请注意,类型类实例是通过匿名let-in
绑定添加到上下文的。显然,这足以满足Coq的类型类解析机制。
Require Import Strings.String.
Local Open Scope list_scope.
Local Open Scope string_scope.
Class Show (A : Type) : Type :=
{
show1 : A -> string
; show2 : A -> string
}.
Definition magicShow1 {A : Type} `{Show A} (a : A) : string :=
"** " ++ show1 a ++ " **".
Definition magicShow2 {A : Type} `{Show A} (a : A) : string :=
"** " ++ show2 a ++ " **".
Fixpoint show1__list {A : Type} `{Show A} (l : list A) : string :=
let _ := (@Build_Show _ show1__list show2__list) in
match l with
| nil => "[]"
| x :: xs => show1 x ++ " : " ++ magicShow1 xs
end
with show2__list {A : Type} `{Show A} (l : list A) : string :=
let _ := (@Build_Show _ show1__list show2__list) in
match l with
| nil => "[]"
| x :: xs => show1 x ++ " : " ++ magicShow2 xs
end.
Instance showMagicList {A : Type} `{Show A} : Show (list A) :=
{
show1 := show1__list
; show2 := show2__list
}.
那么您为什么要这样做?一个很好的例子是当您在(玫瑰)树上定义可判定的相等性时。在定义的中间,我们必须递归地呼吁list (tree A)
的可判定相等性。我们想使用标准的库辅助函数Coq.Classes.EquivDec.list_eqdec
[2],该函数显示了如何将类型A
的可判定相等性传递给list A
。由于list_eqdec
需要一个类型类实例(我们正处于定义类实例中),因此我们必须使用上面的相同技巧:
Require Import Coq.Classes.EquivDec.
Require Import Coq.Program.Utils.
Set Implicit Arguments.
Generalizable Variables A.
Inductive tree (A : Type) : Type :=
| leaf : A -> tree A
| node : list (tree A) -> tree A.
Program Instance tree_eqdec `(eqa : EqDec A eq) : EqDec (tree A) eq :=
{ equiv_dec := fix tequiv t1 t2 :=
let _ := list_eqdec tequiv in
match t1, t2 with
| leaf a1, leaf a2 =>
if a1 == a2 then in_left else in_right
| node ts1, node ts2 =>
if ts1 == ts2 then in_left else in_right
| _, _ => in_right
end
}.
Solve Obligations with unfold not, equiv, complement in * ;
program_simpl ; intuition (discriminate || eauto).
Next Obligation.
destruct t1;
destruct t2;
( program_simpl || unfold complement, not, equiv in *; eauto ).
Qed.
Solve Obligations with split; (intros; try unfold complement, equiv ; program_simpl).
(*
No more obligations remaining
tree_eqdec is defined
*)
注释:没有用于创建类型为EqDec
的记录的构造函数(因为它只有一个类方法),因此要说服Coq list (tree A)
具有可判定的相等性,调用就是{{ 1}}。对于初学者来说,list_eqdec tequiv
只是允许实例定义中的空缺在以后以Program
的形式填充,这比内联编写适当的证明更方便。