英文:
Defining groups in Idris
问题
我在Idris中定义了单半群(monoid)如下:
interface Is_monoid (ty : Type) (op : ty -> ty -> ty) where
id_elem : () -> ty
proof_of_left_id : (a : ty) -> ((op a (id_elem ())) = a)
proof_of_right_id : (a : ty) -> ((op (id_elem ()) a) = a)
proof_of_associativity : (a, b, c : ty) -> ((op a (op b c)) = (op (op a b) c))
然后尝试定义群(group)如下:
interface (Is_monoid ty op) => Is_group (ty : Type) (op : ty -> ty -> ty) where
inverse : ty -> ty
proof_of_left_inverse : (a : ty) -> (a = (id_elem ()))
但在编译时出现了以下错误:
When checking type of Group.proof_of_left_inverse:
Can't find implementation for Is_monoid ty op
有没有解决方法?
英文:
I defined monoid in Idris as
interface Is_monoid (ty : Type) (op : ty -> ty -> ty) where
id_elem : () -> ty
proof_of_left_id : (a : ty) -> ((op a (id_elem ())) = a)
proof_of_right_id : (a : ty) -> ((op (id_elem ())a) = a)
proof_of_associativity : (a, b, c : ty) -> ((op a (op b c)) = (op (op a b) c))
then tried to define groups as
interface (Is_monoid ty op) => Is_group (ty : Type) (op : ty -> ty -> ty) where
inverse : ty -> ty
proof_of_left_inverse : (a : ty) -> (a = (id_elem ()))
but during compilation it showed
When checking type of Group.proof_of_left_inverse:
Can't find implementation for Is_monoid ty op
Is there a way around it.
答案1
得分: 2
错误信息有点误导,但事实上,编译器不知道在你的proof_of_left_inverse
定义中对id_elem
的调用应该使用哪个Is_monoid
的实现。你可以通过更明确地进行调用来使其工作:
proof_of_left_inverse : (a : ty) -> (a = (id_elem {ty = ty} {op = op} ()))
现在,为什么需要这样做呢?如果我们有一个简单的接口,比如:
interface Pointed a where
x : a
我们可以直接编写一个函数,例如:
origin : (Pointed b) => b
origin = x
而无需明确指定任何类型参数。
理解这一点的一种方法是通过其他更基本的Idris功能来看待接口和实现。x
可以被看作是一个函数:
x : {a : Type} -> {auto p : PointedImpl a} -> a
其中,PointedImpl
是表示Pointed
的实现的伪类型(类似于函数的记录)。
类似地,origin
看起来是这样的:
origin : {b : Type} -> {auto j : PointedImpl b} -> b
x
有两个隐式参数,编译器在类型检查和一致性检查期间会尝试推断它们。在上面的例子中,我们知道origin
必须返回b
,因此我们可以将a
与b
统一起来。
现在,i
也是auto
,所以它不仅会进行一致性检查(在这里没有帮助),而且编译器还会寻找可以填充该空白的“周围值”,如果没有显式指定的话。在本地变量之后查找的第一个地方就是参数列表,那里我们确实找到了j
。
因此,我们对origin
的调用可以解析,而无需明确指定任何附加参数。
你的情况更类似于以下示例:
interface Test a b where
x : a
y : b
test : (Test c d) => c
test = x
这将出现与你的示例相同的错误。按照上面的步骤,我们可以编写如下:
x : {a : Type} -> {b -> Type} -> {auto i : TestImpl a b} -> a
test : {c : Type} -> {d -> Type} -> {auto j : TestImpl c d} -> c
与上面一样,我们可以统一a
和c
,但没有任何东西告诉我们d
应该是什么。具体来说,我们不能将其与b
统一起来,因此我们不能将TestImpl a b
与TestImpl c d
统一起来,因此我们不能使用j
作为auto
参数i
的值。
请注意,我不是在声称这就是底层实现方式。这只是一种类比,但至少在某种程度上是合理的。
英文:
The error message is a bit misleading, but indeed, the compiler does not know which implementation of Is_monoid
to use for your call to id_elem
in your definition of proof_of_left_inverse
. You can make it work by making it making the call more explicit:
proof_of_left_inverse : (a : ty) -> (a = (id_elem {ty = ty} {op = op} ()))
Now, why is this necessary? If we have a simple interface like
interface Pointed a where
x : a
we can just write a function like
origin : (Pointed b) => b
origin = x
without specifying any type parameters explicitly.
One way to understand this is to look at interfaces and implementations through the lens of other, in a way more basic Idris features. x
can be thought of as a function
x : {a : Type} -> {auto p : PointedImpl a} -> a
where PointedImpl
is some pseudo type that represents the implementations of Pointed
. (Think a record of functions.)
Similarly, origin
looks something like
origin : {b : Type} -> {auto j : PointedImpl b} -> b
x
notably has two implicit arguments, which the compiler tries to infer during type checking and unification. In the above example, we know that origin
has to return a b
, so we can unify a
with b
.
Now i
is also auto
, so it is not only subject to unification (which does not help here), but in addition, the compiler looks for "surrounding values" that can fill that hole if no explicit one was specified. The first place to look after local variables which we don't have is the parameter list, where we indeed find j
.
Thus, our call to origin
resolves without us having to explicitly specify any additional arguments.
Your case is more akin to this:
interface Test a b where
x : a
y : b
test : (Test c d) => c
test = x
This will error in the same manner your example did. Going through the same steps as above, we can write
x : {a : Type} -> {b -> Type} -> {auto i : TestImpl a b} -> a
test : {c : Type} -> {d -> Type} -> {auto j : TestImpl c d} -> c
As above, we can unify a
and c
, but there is nothing that tells us what d
is supposed to be. Specifically, we can't unify it with b
, and consequently we can't unify TestImpl a b
with TestImpl c d
and thus we can't use j
as value for the auto
-parameter i
.
Note that I don't claim that this is how things are implemented under the covers. This is just an analogy in a sense, but one that holds up to at least some scrutiny.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论