[英]How does agda pattern match with same typed constructors?
I have started working my way through the Programming Language Foundations in Agda book and have become confused while attempting to perform the exercise which asks you to write an inc
function which increments a binary number represented by the following Bin
type:我已经开始研究Agda 中的 Programming Language Foundations书,并且在尝试执行要求您编写inc
function 的练习时感到困惑,该练习会增加由以下Bin
类型表示的二进制数:
data Bin : Set where
⟨⟩ : Bin
_O : Bin → Bin
_I : Bin → Bin
I started writing my inc
function as我开始将我的inc
function 写为
inc_ : Bin → Bin
inc ⟨⟩ = ⟨⟩
inc (x O) = {!!}
inc (x I) = {!!}
and then thought: "How does Agda know if a value of type Bin
can be matched and destructed into x O
and x I
without ambiguity if the _O
and _I
constructors have the same type?".然后想:“如果_O
和_I
构造函数具有相同的类型,Agda 怎么知道Bin
类型的值是否可以匹配并分解为x O
和x I
而没有歧义?”。 The more I thought about this the more confused I became, if we take the Nat
example:如果我们以Nat
为例,我越想越困惑:
data ℕ : Set where
zero : ℕ
succ : ℕ → ℕ
the zero
and succ
constructors both produce a value of type ℕ
, so how does Agda know whether some particular value of type ℕ
would match the patterns I have in place for functions such as _+_
? zero
和succ
构造函数都产生ℕ
类型的值,那么 Agda 如何知道ℕ
类型的某些特定值是否与我为_+_
等函数设置的模式匹配?
_+_ : ℕ → ℕ → ℕ
zero + m = m
succ n-1 + m = succ (n-1 + m)
It seems to me the only way this could work is if Agda keeps track of the constructor used to create each value, and then allows for using that same constructor in pattern matching?在我看来,唯一可行的方法是,如果 Agda 跟踪用于创建每个值的构造函数,然后允许在模式匹配中使用相同的构造函数?
I am very new to Agda and trying to wrap my head around the relationship between types, values, constructors and pattern matching.我对 Agda 很陌生,并试图围绕类型、值、构造函数和模式匹配之间的关系展开思考。 I would very much appreciate an explanation of how these relate with reference to the Bin
and ℕ
examples in my question.我非常感谢您参考我的问题中的Bin
和ℕ
示例来解释这些之间的关系。 I have tried reading some additional articles/books/lecture sides and the Agda docs but I could not find an answer to this question.我已经尝试阅读一些额外的文章/书籍/讲座和 Agda 文档,但我找不到这个问题的答案。
This issue first arises with a very simple type – the Booleans:这个问题首先出现在一个非常简单的类型——布尔值:
data Bool : Set where
true : Bool
false : Bool
We can write functions out of Bool
by pattern matching, for example:我们可以通过模式匹配从Bool
中写出函数,例如:
not : Bool → Bool
not true = false
not false = true
Bool
, like similar types in other programming languages, is a type with two canonical inhabitants: true
and false
. Bool
与其他编程语言中的类似类型一样,是具有两个规范居民的类型: true
和false
。 A Bool
value stores 1 bit of information.一个Bool
值存储 1 位信息。 At run time, and also for the sake of computation at type checking time, we could imagine that there literally is a bit in the specified memory location indicating whether the value is true
or false
.在运行时,也为了在类型检查时进行计算,我们可以想象在指定的 memory 位置中有一个位指示该值是true
还是false
。
In terms of data
declarations, this bit comes about because Bool
has two constructors.在data
声明方面,这个位的出现是因为Bool
有两个构造函数。 Similarly, ℕ
has two constructors, so that will also contain a bit indicating whether the value is a zero
or suc
(and if it is a suc
, there will also be a pointer to the rest of the number).同样, ℕ
有两个构造函数,因此它还将包含一个位,指示该值是zero
还是suc
(如果是suc
,还将有一个指向数字的 rest 的指针)。 For Bin
, we will store a 0, 1, or 2, to indicate whether we have a ⟨⟩
, _O
, or _I
.对于Bin
,我们将存储 0、1 或 2,以指示我们是否有⟨⟩
、 _O
或_I
。
It is this extra information (one or two bits for the examples here) that pattern matching relies on.模式匹配所依赖的正是这些额外信息(此处的示例为一个或两个位)。 In fact, types are typically erased, so pattern matching couldn't use them.事实上,类型通常会被删除,因此模式匹配无法使用它们。 As such, not
is essentially compiled to something like the following pseudo-C.因此, not
本质上被编译为类似于以下伪 C 的东西。
int* not(int* b) {
switch (*b) { // Look at the tag of b.
case 0: // If b is true,
int* r = malloc(sizeof(int)); // Allocate a new boolean;
*r = 1; // Set the value of the new boolean to false;
return r; // Return the new boolean.
case 1: // If b is false,
int* r = malloc(sizeof(int)); // Allocate a new boolean;
*r = 0; // Set the value of the new boolean to false;
return r; // Return the new boolean.
}
}
Meanwhile, the _+_
function on ℕ
will be compiled into something like:同时, ℕ
上的_+_
function 将被编译成如下内容:
int* add(int* n, int* m) {
switch (*n) { // Look at the tag of n.
case 0: // If n is zero,
return m; // Return m.
case 1: // If n is a suc,
int* r = malloc(2 * sizeof(int)); // Allocate space for a suc cell;
*r = 1; // Indicate that it is a suc by setting the tag to 1;
*(r+1) = add(n+1, m); // After the tag is a pointer, set to the result of the recursive call to add.
return r; // Return the new ℕ.
}
}
Note that n+1
here refers to the memory location after n
's tag, ie, the pointer to the predecessor of n
( n-1
in the Agda code).注意这里的n+1
是指n
的tag后面的memory位置,即指向n
的前身的指针(Agda代码中的n-1
)。 I'm assuming for simplicity that sizeof(int) = sizeof(int*)
, and it's fine to store pointers in the int
type.为简单起见,我假设sizeof(int) = sizeof(int*)
,并且可以将指针存储在int
类型中。 The key detail is that we always tag data
values with which constructor they are made of, and having done this, branching via pattern matching amounts to looking at this tag.关键细节是我们总是用它们构成的构造函数来标记data
值,并且这样做之后,通过模式匹配进行分支相当于查看这个标记。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.