在Idris中定义群组

huangapple go评论67阅读模式
英文:

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,因此我们可以将ab统一起来。

现在,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

与上面一样,我们可以统一ac,但没有任何东西告诉我们d应该是什么。具体来说,我们不能将其与b统一起来,因此我们不能将TestImpl a bTestImpl 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.

huangapple
  • 本文由 发表于 2020年1月4日 01:18:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/59582709.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定