类型类有什么问题?

时间:2022-04-27 16:55:42

Type classes seem to be a great possibility to write generic and reusable functions in a very consistent, efficient and extensible way. But still no "mainstream-language" provides them - On the contrary: Concepts, which are a quite analogical idea, have been excluded from the next C++!

类型类似乎很有可能以非常一致,高效和可扩展的方式编写泛型和可重用函数。但仍然没有“主流语言”提供它们 - 相反:概念,这是一个非常类比的想法,已被排除在下一个C ++之外!

What's the reasoning against typeclasses? Apparently many languages are looking for a way to deal with similar problems: .NET introduced generic constraints and interfaces like IComparable which allow functions like

反对类型类的原因是什么?显然,许多语言正在寻找一种方法来处理类似的问题:.NET引入了通用约束和类似IComparable的接口,它们允许像

T Max<T>(T a, T b) where T : IComparable<T> { // }

to operate on all types that implement the interface.

操作所有实现接口的类型。

Scala instead uses a combination of traits and so called implicit parameters/view bounds, which are automatically passed to generic functions.

Scala使用traits和所谓的隐式参数/视图边界的组合,它们会自动传递给泛型函数。

But both concepts shown here have great disadvantages - Interfaces are inheritance-based and thus relatively slow due to indirection and moreover there is no possibility of letting an existing type implement them.

但是这里显示的两个概念都有很大的缺点 - 接口是基于继承的,因此由于间接而相对较慢,而且不可能让现有类型实现它们。

If we needed an abstraction for a Monoid, we could pretty well write an interface and let our types implement this, but builtin types like int could never operate on your functions natively.

如果我们需要Monoid的抽象,我们可以很好地编写一个接口并让我们的类型实现它,但像int这样的内置类型本身就不能对你的函数进行操作。

Implicit parameters instead are inconsistent with regular interfaces/traits.

相反,隐式参数与常规接口/特征不一致。

With type classes, there wouldn't be a problem (pseudo-code)

使用类型类,不会有问题(伪代码)

typeclass Monoid of A where
    static operator (+) (x : A, y : A) : A
    static val Zero : A 
end

instance Int of Monoid where
   static operator (+) (x : Int, y : Int) : Int = x + y
   static val Zero : Int = 0
end

So why don't we use type classes? Do they have serious disadvantages after all?

那么我们为什么不使用类型类呢?毕竟他们有严重的劣势吗?

Edit: Please don't confuse typeclasses with structural typing, pure C++ templates or duck typing. A typeclass is explicitly instantiated by types and not just satisfied by convention. Moreover it can carry useful implementations and not just define an interface.

编辑:请不要将类型类与结构类型,纯C ++模板或鸭子类型混淆。类型类通过类型显式实例化,而不仅仅是按惯例满足。此外,它可以携带有用的实现,而不仅仅是定义一个接口。

6 个解决方案

#1


9  

Concepts were excluded because the committee didn't think it could get them right in time, and because they weren't considered essential to the release. It's not that they don't think they're a good idea, they just don't think the expression of them for C++ is mature: http://herbsutter.wordpress.com/2009/07/21/trip-report/

概念被排除在外,因为委员会认为它不能及时使它们正确,并且因为它们不被认为对发布至关重要。并不是说他们认为这不是一个好主意,他们只是不认为他们对C ++的表达是成熟的:http://herbsutter.wordpress.com/2009/07/21/trip-report/

Static types try to prevent you passing an object to a function, that doesn't satisfy the requirements of the function. In C++ this is a huge big deal, because at the time the object is accessed by the code, there's no checking that it's the right thing.

静态类型试图阻止您将对象传递给函数,该函数不满足函数的要求。在C ++中,这是一个非常重要的事情,因为当代码访问对象时,没有检查它是否正确。

Concepts try to prevent you passing a template parameter, that doesn't satisfy the requirements of the template. But at the time the template parameter is accessed by the compiler, there already is checking that it's the right thing, even without Concepts. If you try to use it in a way it doesn't support, you get a compiler error[*]. In the case of heavy template-using code you might get three screens full of angle brackets, but in principle that's an informative message. The need to catch errors before a failed compile is less urgent than the need to catch errors before undefined behaviour at runtime.

Concepts尝试阻止您传递模板参数,该参数不满足模板的要求。但是当编译器访问模板参数时,即使没有Concepts,也已经检查它是否正确。如果您尝试以不支持的方式使用它,则会出现编译器错误[*]。在使用大量模板代码的情况下,您可能会得到三个充满尖括号的屏幕,但原则上这是一条信息性消息。在编译失败之前捕获错误的需要不如在运行时未定义行为之前捕获错误的需要。

Concepts make it easier to specify template interfaces that will work across multiple instantiations. This is significant, but a much less pressing problem than specifying function interfaces that will work across multiple calls.

通过概念,可以更轻松地指定可在多个实例中工作的模板接口。这很重要,但是比指定可以跨多个调用工作的函数接口要紧迫得多。

In answer to your question - any formal statement "I implement this interface" has one big disadvantage, that it requires the interface to be invented before the implementation is. Type inference systems don't, but they have the big disadvantage that languages in general cannot express the whole of an interface using types, and so you might have an object which is inferred to be of the correct type, but which does not have the semantics ascribed to that type. If your language addresses interfaces at all (in particular if it matches them to classes), then AFAIK you have to take a stance here, and pick your disadvantage.

在回答你的问题时 - 任何正式声明“我实现这个接口”都有一个很大的缺点,那就是它需要在实现之前发明接口。类型推断系统没有,但它们有一个很大的缺点,即语言一般不能用类型表示整个接口,所以你可能有一个被推断为正确类型的对象,但没有归因于该类型的语义。如果您的语言完全解决了界面(特别是如果它与课程相匹配),那么AFAIK你必须在这里采取立场,并选择你的劣势。

[*] Usually. There are some exceptions, for instance the C++ type system currently does not prevent you from using an input iterator as if it were a forward iterator. You need iterator traits for that. Duck typing alone doesn't stop you passing an object which walks, swims and quacks, but on close inspection doesn't actually do any of those things the way a duck does, and is astonished to learn that you thought it would ;-)

[*]通常。有一些例外,例如C ++类型系统目前不会阻止您使用输入迭代器,就像它是一个前向迭代器一样。你需要迭代器特性。单独打鸭并不能阻止你通过一个走路,游泳和嘎嘎叫的物体,但仔细检查实际上并没有像鸭子那样做任何事情,并且惊讶地发现你认为它会这样;-)

#2


4  

Interfaces need not be inheritance-based... that's a different and separate design decision. The new Go language has interfaces, but doesn't have inheritance, for example: "a type automatically satisfies any interface that specifies a subset of its methods", as the Go FAQ puts it. Simionato's musings about inheritance and interfaces, prompted by Go's recent release, may be worth reading.

接口不必是基于继承的......这是一个不同的独立设计决策。新的Go语言有接口,但没有继承,例如:“一种类型自动满足指定其方法子集的任何接口”,正如Go FAQ所说的那样。由Go最近发布的,Simionato关于继承和接口的思考可能值得一读。

I concur that typeclasses are even more powerful, essentially because, like abstract base classes, they let you additionally specify useful code (defining an extra method X in terms of others for all types that otherwise match the baseclass but don't define X themselves) -- without the inheritance baggage that ABCs (differently from interfaces) almost inevitably carry. Almost inevitably because, for example, Python's ABCs "make believe" that they involve inheritance, in terms of the conceptualization they offer... but, in fact, they need not be inheritance-based (many are just checking the presence and signature of certain methods, just like Go's interfaces).

我同意类型类更强大,主要是因为像抽象基类一样,它们允许你另外指定有用的代码(为所有类型定义一个额外的方法X,否则所有类型都匹配基类但不自己定义X) - 没有继承包袱,ABCs(不同于接口)几乎不可避免地携带。几乎不可避免地,因为,例如,Python的ABCs“相信”它们涉及继承,就它们提供的概念化而言......但实际上,它们不需要基于继承(许多只是检查存在和签名)某些方法,就像Go的接口一样)。

As for why would a language designer (such as Guido, in the case of Python) choose such "wolves in sheep clothing" as Python's ABCs, over the more straightforward Haskell-like typeclasses that I had proposed since back in 2002, that's a harder question to answer. After all, it isn't as if Python has any compunction against borrowing concepts from Haskell (e.g., list comprehensions / generator expressions -- Python needs a duality here, while Haskell doesn't, because Haskell is "lazy"). The best hypothesis I can offer is that, by now, inheritance is so familiar to most programmers that most language designers feel they can gain easier acceptance by casting things that way (although Go's designers must be commended for not doing that).

至于为什么一个语言设计师(比如Guido,在Python的情况下)选择这样的“绵羊服装狼”作为Python的ABCs,而不是我自2002年以来提出的更简单的类似Haskell的类型类,那就更难了问题要回答。毕竟,并不是说Python对Haskell的借用概念有任何反对意义(例如,列表推导/生成器表达式 - Python在这里需要二元性,而Haskell则不需要,因为Haskell是“懒惰的”)。我能提供的最好的假设是,到目前为止,大多数程序员都非常熟悉继承,以至于大多数语言设计师都认为通过这样的方式可以更容易地接受(尽管Go的设计者必须因为不这样做而受到赞扬)。

#3


1  

Let me start bold: I perfectly understand the motivation of having it and can't understand the motivation of some people to argue against it...

让我开始大胆:我完全理解拥有它的动机,并且无法理解某些人反对它的动机......

What you want is nonvirtual ad hoc polymorphism.

你想要的是非虚拟ad hoc多态。

  • ad hoc: implementation can vary
  • 临时:实施可能有所不同

  • nonvirtual: for performance reasons; compiletime dispatch
  • 非虚拟的:出于性能原因;编译时发送

The rest is sugar in my opinion.

其余的是我认为的糖。

C++ already has ad hoc polymorphism via templates. "Concepts" however would clarify what kind of ad hoc polymorphic functionality is used by which user defined entity.

C ++已经通过模板具有ad hoc多态性。然而,“概念”将阐明哪个用户定义的实体使用什么类型的特殊多态功能。

C# just doesn't have any way to do it. An approach that wouldn't be nonvirtual: If types like float would just implement something like "INumeric" or "IAddable" (...) we would at least be able to write a generic min, max, lerp and based on that clamp, maprange, bezier (...). However it wouldn't be fast. You dont want that.

C#只是没有办法做到这一点。一种不是非虚拟的方法:如果像float这样的类型只是实现“INumeric”或“IAddable”(...)之类的东西,我们至少可以编写一个通用的min,max,lerp并基于该钳位,maprange,bezier(...)。然而,它不会很快。你不想那样。

Ways of fixing this: Since .NET does JIT compilation anyway also generates different code for List<int> than for List<MyClass> (due to the differences of value and reference types) it probably wouldn't add that much of an overhead to also generate different code for the ad hoc polymorphic parts. The C# language would just need a way to express it. One way is what you sketched up.

解决这个问题的方法:由于.NET无论如何JIT编译也为List 生成不同于List 的代码(由于值和引用类型的不同),它可能不会增加大量的开销还为ad hoc多态部分生成不同的代码。 C#语言只需要一种方式来表达它。一种方法是你勾勒出来的。

Another way would be to add type constraints to the function using an ad hoc polymorphic function:

另一种方法是使用ad hoc多态函数向函数添加类型约束:

    U SuperSquare<T, U>(T a) applying{ 
         nonvirtual operator (*) T (T, T) 
         nonvirtual Foo U (T)
    }
    {
        return Foo(a * a);
    }

Of course you could end up with more and more constraints when implementing Bar that uses Foo. So you may want a mechanism to give a name to several constraints that you regularly use... However this again is sugar and one way to approach it would be to just use the typeclass concept...

当然,在实现使用Foo的Bar时,最终会遇到越来越多的限制。因此,您可能需要一种机制来为您经常使用的几个约束命名...然而,这又是糖,一种接近它的方法就是使用类型类概念......

Giving a name to several constraints is like defining a type class, but i'd like to just look at it as some sort of abbreviation mechanism - sugar for an arbitrary collection of function type constraints:

为几个约束命名就像定义一个类型类,但我想把它看作某种缩写机制 - 用于函数类型约束的任意集合的糖:

    // adhoc is like an interface: it is about collecting signatures
    // but it is not a type: it dissolves during compilation 
    adhoc AMyNeeds<T, U>
    {
         nonvirtual operator (*) T (T, T) 
         nonvirtual Foo U (T)
    } 

    U SuperSquare<T, U>(T a) applying AMyNeeds<T, U>        
    {
        return Foo(a * a);
    }

At some place "main" all the type arguments are known and everything becomes concrete and can be compiled together.

在某些地方“主”,所有类型参数都是已知的,一切都变得具体,可以一起编译。

What's missing still is the lack of creating different implementations. In the upper example we just used polymorphic functions and let everybody know...

缺少的是缺乏创建不同的实现。在上面的例子中,我们只使用了多态函数,让大家知道......

Implementation then again could follow the way of extension methods - in their ability to add functionality to any class at any point:

然后再实现可以遵循扩展方法的方式 - 在任何时候向任何类添加功能的能力:

 public static class SomeAdhocImplementations
 {
    public nonvirtual int Foo(float x)
    {
        return round(x);
    }
 }

In main you now can write:

在主要你现在可以写:

    int a = SuperSquare(3.0f); // 3.0 * 3.0 = 9.0 rounded should return 9

The compiler checks all "nonvirtual" ad hoc functions, finds both a built-in float (*) operator and a int Foo (float) and therefore is able to compile that line.

编译器检查所​​有“非虚拟”ad hoc函数,找到内置的float(*)运算符和int Foo(float),因此能够编译该行。

Ad hoc polymorphism of course comes with the downside that you have to recompile for each compile time type so that the right implementations get inserted. And probably IL doesn't support that being put into a dll. But maybe they work on it anyway...

Ad hoc多态性当然伴随着你必须为每个编译时类型重新编译的缺点,以便插入正确的实现。并且IL可能不支持将其放入dll中。但也许他们无论如何都要努力......

I see no real need for instanciation of a type class construct. If anything would fail on compilation we get the errors of the constraints or if those were boundled together with an "adhoc" codeclock the error message could get even more readable.

我认为没有真正需要类型类构造的实例化。如果编译时出现任何问题,我们会得到约束的错误,或者如果这些错误与“adhoc”代码时钟绑定在一起,则错误消息可能更具可读性。

    MyColor a = SuperSquare(3.0f); 
    // error: There are no ad hoc implementations of AMyNeeds<float, MyColor> 
    // in particular there is no implementation for MyColor Foo(float)

But of course also the instanciation of a type class / "adhoc polymorphism interface" is thinkable. The error message would then state: "The AMyNeeds constraint of SuperSquare has not been matched. AMyNeeds is available as StandardNeeds : AMyNeeds<float, int> as defined in MyStandardLib". It also would be possible to put the implementation in a class together with other methods and add the "adhoc interface" to the list of supported interfaces.

但当然,类型类/“adhoc多态性接口”的实例化也是可以想象的。然后,错误消息将声明:“SuperSquare的AMyNeeds约束尚未匹配.AMyNeeds可用作StandardNeeds:AMyNeeds ,如MyStandardLib中所定义”。也可以将实现与其他方法一起放在一个类中,并将“adhoc接口”添加到支持的接口列表中。 ,int>

But independant of the particular language design: i don't see the downside of adding them one way or the other. Save statically typed languages will always need to push the boundary of expressive power, since they started by allowing too little, which tends to be a smaller set of expressive power a normal programmer would have expected to be possible...

但独立于特定的语言设计:我没有看到以某种方式添加它们的缺点。保存静态类型语言总是需要突破表达能力的边界,因为它们通过允许太少而开始,这往往是正常程序员预期可能的一小部分表达能力......

tldr: i am on your side. Stuff like this sucks in mainstream statically typed languages. Haskell showed the way.

tldr:我在你身边。像这样的东西在主流的静态类型语言中很糟糕。哈斯克尔表明了道路。

#4


0  

What's the reasoning against typeclasses?

反对类型类的原因是什么?

Implementation complexity for compiler writers is always a concern when considering new language features. C++ already made that mistake and we've already suffered years of buggy C++ compilers as a consequence.

在考虑新的语言特性时,编译器编写器的实现复杂性始终是一个问题。 C ++已经犯了这个错误,因此我们已经遭遇了多年的错误C ++编译器。

Interfaces are inheritance-based and thus relatively slow due to indirection and moreover there is no possibility of letting an existing type implement them

接口是基于继承的,因此由于间接而相对较慢,而且不可能让现有类型实现它们

Not true. Look at OCaml's structurally-typed object system, for example:

不对。查看OCaml的结构类型对象系统,例如:

# let foo obj = obj#bar;;
val foo : < bar : 'a; .. > -> 'a = <fun>

That foo function accepts any object of any type that provides the necessary bar method.

该foo函数接受任何提供必要bar方法的任何类型的对象。

Same for ML's higher-order module system. Indeed, there is even a formal equivalence between that and type classes. In practice, type classes are better for small scale abstractions such as operator overloading whereas higher-order modules are better for large scale abstractions such as Okasaki's parameterization of catenable lists over queues.

ML的高阶模块系统也是如此。实际上,在类和类之间甚至存在形式上的等价。实际上,类型类对于小规模抽象(例如运算符重载)更好,而高阶模块更适合大规模抽象,例如Okasaki对队列中的可连接列表的参数化。

Do they have serious disadvantages after all?

毕竟他们有严重的劣势吗?

Look at your own example, generic arithmetic. F# can actually already handle that particular case thanks to the INumeric interface. The F# Matrix type even uses that approach.

看看你自己的例子,通用算术。由于INumeric接口,F#实际上已经可以处理该特定情况。 F#Matrix类型甚至使用该方法。

However, you've just replaced the machine code for add with dynamic dispatch to a separate function, making arithmetic orders of magnitude slower. For most applications, that is uselessly slow. You can solve that problem by performing whole program optimizations but that has obvious disadvantages. Moreover, there is little commonality between numerical methods for int vs float due to numerical robustness so your abstraction is also practically useless.

但是,您刚刚将用于添加动态调度的机器代码替换为单独的函数,从而使算术数量级更慢。对于大多数应用程序来说,这是无用的慢。您可以通过执行整个程序优化来解决该问题,但这有明显的缺点。此外,由于数值稳健性,int与float的数值方法之间几乎没有共性,因此您的抽象实际上也是无用的。

The question should surely be: can anyone make a compelling case for the adoption of type classes?

问题当然应该是:任何人都可以为采用类型类做出令人信服的理由吗?

#5


0  

But still no "mainstream-language" provides [type classes.]

但仍然没有“主流语言”提供[类型类]。

When this question was asked, this might have been true. Today, there is a much stronger interest in languages like Haskell and Clojure. Haskell has type classes (class / instance), Clojure 1.2+ has protocols (defprotocol / extend).

当问到这个问题时,这可能是真的。今天,人们对Haskell和Clojure等语言的兴趣更浓厚。 Haskell有类型类(类/实例),Clojure 1.2+有协议(defprotocol / extend)。

What's the reasoning against [type classes]?

反对[类型]的原因是什么?

I don't think that type classes are objectively "worse" than other polymorphism mechanisms; they just follow a different approach. So the real question is, do they fit well into a particular programming language?

我不认为类型类比其他多态机制客观地“更差”;他们只是采用不同的方法。所以真正的问题是,它们是否适合特定的编程语言?

Let's briefly consider how type classes are different from interfaces in languages such as Java or C#. In these languages, a class only supports interfaces that are explicitly mentioned and implemented in that class' definition. Type classes, however, are interfaces that can be later appended to any already defined type, even in another module. This kind of type extensibility is obviously quite different from the mechanisms in certain "mainstream" OO languages.

让我们简单地考虑一下类型类与Java或C#等语言中的接口有何不同。在这些语言中,类仅支持在该类定义中明确提及和实现的接口。但是,类型类是可以稍后附加到任何已定义类型的接口,即使在另一个模块中也是如此。这种类型的可扩展性与某些“主流”OO语言的机制明显不同。


Let's now consider type classes for a few mainstream programming languages.

现在让我们考虑一些主流编程语言的类型类。

Haskell: No need to say that this language has type classes.

Haskell:不需要说这种语言有类型类。

Clojure: As said above, Clojure has something like type classes in the form of protocols.

Clojure:如上所述,Clojure有类似于协议形式的类型。

C++: As you've said yourself, concepts were dropped from the C++11 specification.

C ++:正如您自己所说,概念已从C ++ 11规范中删除。

On the contrary: Concepts, which are a quite analogical idea, have been excluded from the next C++!

恰恰相反:Concept是一个非常类比的概念,已被排除在下一个C ++之外!

I haven't followed the whole debate around this decision. From what I've read, concepts weren't "ready yet": There was still debate about concept maps. However, concepts weren't given up completely, it is expected that they will make it into the next version of C++.

我没有围绕这个决定进行整个辩论。从我所读到的,概念还没有“准备就绪”:关于概念图仍然存在争议。但是,概念并没有完全放弃,预计它们将成为C ++的下一个版本。

C#: With language version 3, C# has essentially become a hybrid of the object-oriented and functional programming paradigms. One addition was made to the language that is conceptually quite similar to type classes: extension methods. The main difference is that you're (seemingly) attaching new methods to an existing type, not interfaces.

C#:使用语言版本3,C#基本上成为面向对象和函数式编程范例的混合体。对概念上与类型类非常相似的语言进行了一处补充:扩展方法。主要区别在于您(似乎)将新方法附加到现有类型,而不是接口。

(Granted, the extension method mechanism is not nearly as elegant as Haskell's instance … where syntax. Extensions methods are not "truly" attached to a type, they are implemented as a syntactical transformation. In the end however, this doesn't make a very big practical difference.)

(当然,扩展方法机制并不像Haskell的实例那样优雅......语法。扩展方法不是“真正”附加到类型,它们是作为语法转换实现的。但最终,这并不是很大的实际差异。)

I don't think this is going to happen anytime soon — the language designers probably won't even add extension properties to the language, and extension interfaces would even go a step further than that.

我认为这不会很快发生 - 语言设计者可能甚至不会在语言中添加扩展属性,扩展接口甚至会更进一步。

(VB.NET: Microsoft has been "co-evolving" the C# and VB.NET languages for some time, so my statements about C# happen to be valid for VB.NET, too.)

(VB.NET:微软已经将C#和VB.NET语言“共同发展”了一段时间,所以我对C#的陈述恰好也适用于VB.NET。)

Java: I don't know Java very well, but of the languages C++, C# and Java, it is probably the "purest" OO language. I don't see how type classes would fit naturally into this language.

Java:我不太了解Java,但是在C ++,C#和Java语言中,它可能是“最纯粹的”OO语言。我没有看到类型类如何适合这种语言。

F#: I've found a forum post explaining why type classes might never get introduced into F#. That explanation centers around the fact that F# has a nominative, and not a structural type system. (Though I'm not certain whether this is a sufficient reason for F# not to have type classes.)

F#:我发现了一个论坛帖子,解释了为什么类型类可能永远不会被引入F#。这种解释围绕着这样一个事实:F#具有主格,而不是结构类型系统。 (虽然我不确定这是否是F#没有类型类的充分理由。)

#6


-3  

Try define a Matroid, which is what we do (logcally and not orally saying a Matroid), and it's still likely something like a C struct. Liskov principle (latest turing medalist) gets too abstract, too categorical, too theoretic, less treating actual data and more pure theoretical class-system, for handson pragmatic problemsolving, briefly glanced it which looked like PROLOG, code about code about code about code...while an algorithm describes sequences and travelsals we understand on paper or blackboard. Depends which goal you have, solving problem with minimal code or most abstract.

尝试定义一个Matroid,这就是我们所做的(logcally而不是口头说Matroid),它仍然可能像C结构。利斯科夫原则(最新的图灵奖章获得者)过于抽象,过于绝对,理论太少,对待实际数据和更纯粹的理论类系统,对于实际操作问题的解决,简要地浏览了看起来像PROLOG的代码,关于代码的代码代码。 ..而算法描述了我们在纸上或黑板上理解的序列和旅行。取决于您拥有的目标,用最少的代码或最抽象的方式解决问题。

#1


9  

Concepts were excluded because the committee didn't think it could get them right in time, and because they weren't considered essential to the release. It's not that they don't think they're a good idea, they just don't think the expression of them for C++ is mature: http://herbsutter.wordpress.com/2009/07/21/trip-report/

概念被排除在外,因为委员会认为它不能及时使它们正确,并且因为它们不被认为对发布至关重要。并不是说他们认为这不是一个好主意,他们只是不认为他们对C ++的表达是成熟的:http://herbsutter.wordpress.com/2009/07/21/trip-report/

Static types try to prevent you passing an object to a function, that doesn't satisfy the requirements of the function. In C++ this is a huge big deal, because at the time the object is accessed by the code, there's no checking that it's the right thing.

静态类型试图阻止您将对象传递给函数,该函数不满足函数的要求。在C ++中,这是一个非常重要的事情,因为当代码访问对象时,没有检查它是否正确。

Concepts try to prevent you passing a template parameter, that doesn't satisfy the requirements of the template. But at the time the template parameter is accessed by the compiler, there already is checking that it's the right thing, even without Concepts. If you try to use it in a way it doesn't support, you get a compiler error[*]. In the case of heavy template-using code you might get three screens full of angle brackets, but in principle that's an informative message. The need to catch errors before a failed compile is less urgent than the need to catch errors before undefined behaviour at runtime.

Concepts尝试阻止您传递模板参数,该参数不满足模板的要求。但是当编译器访问模板参数时,即使没有Concepts,也已经检查它是否正确。如果您尝试以不支持的方式使用它,则会出现编译器错误[*]。在使用大量模板代码的情况下,您可能会得到三个充满尖括号的屏幕,但原则上这是一条信息性消息。在编译失败之前捕获错误的需要不如在运行时未定义行为之前捕获错误的需要。

Concepts make it easier to specify template interfaces that will work across multiple instantiations. This is significant, but a much less pressing problem than specifying function interfaces that will work across multiple calls.

通过概念,可以更轻松地指定可在多个实例中工作的模板接口。这很重要,但是比指定可以跨多个调用工作的函数接口要紧迫得多。

In answer to your question - any formal statement "I implement this interface" has one big disadvantage, that it requires the interface to be invented before the implementation is. Type inference systems don't, but they have the big disadvantage that languages in general cannot express the whole of an interface using types, and so you might have an object which is inferred to be of the correct type, but which does not have the semantics ascribed to that type. If your language addresses interfaces at all (in particular if it matches them to classes), then AFAIK you have to take a stance here, and pick your disadvantage.

在回答你的问题时 - 任何正式声明“我实现这个接口”都有一个很大的缺点,那就是它需要在实现之前发明接口。类型推断系统没有,但它们有一个很大的缺点,即语言一般不能用类型表示整个接口,所以你可能有一个被推断为正确类型的对象,但没有归因于该类型的语义。如果您的语言完全解决了界面(特别是如果它与课程相匹配),那么AFAIK你必须在这里采取立场,并选择你的劣势。

[*] Usually. There are some exceptions, for instance the C++ type system currently does not prevent you from using an input iterator as if it were a forward iterator. You need iterator traits for that. Duck typing alone doesn't stop you passing an object which walks, swims and quacks, but on close inspection doesn't actually do any of those things the way a duck does, and is astonished to learn that you thought it would ;-)

[*]通常。有一些例外,例如C ++类型系统目前不会阻止您使用输入迭代器,就像它是一个前向迭代器一样。你需要迭代器特性。单独打鸭并不能阻止你通过一个走路,游泳和嘎嘎叫的物体,但仔细检查实际上并没有像鸭子那样做任何事情,并且惊讶地发现你认为它会这样;-)

#2


4  

Interfaces need not be inheritance-based... that's a different and separate design decision. The new Go language has interfaces, but doesn't have inheritance, for example: "a type automatically satisfies any interface that specifies a subset of its methods", as the Go FAQ puts it. Simionato's musings about inheritance and interfaces, prompted by Go's recent release, may be worth reading.

接口不必是基于继承的......这是一个不同的独立设计决策。新的Go语言有接口,但没有继承,例如:“一种类型自动满足指定其方法子集的任何接口”,正如Go FAQ所说的那样。由Go最近发布的,Simionato关于继承和接口的思考可能值得一读。

I concur that typeclasses are even more powerful, essentially because, like abstract base classes, they let you additionally specify useful code (defining an extra method X in terms of others for all types that otherwise match the baseclass but don't define X themselves) -- without the inheritance baggage that ABCs (differently from interfaces) almost inevitably carry. Almost inevitably because, for example, Python's ABCs "make believe" that they involve inheritance, in terms of the conceptualization they offer... but, in fact, they need not be inheritance-based (many are just checking the presence and signature of certain methods, just like Go's interfaces).

我同意类型类更强大,主要是因为像抽象基类一样,它们允许你另外指定有用的代码(为所有类型定义一个额外的方法X,否则所有类型都匹配基类但不自己定义X) - 没有继承包袱,ABCs(不同于接口)几乎不可避免地携带。几乎不可避免地,因为,例如,Python的ABCs“相信”它们涉及继承,就它们提供的概念化而言......但实际上,它们不需要基于继承(许多只是检查存在和签名)某些方法,就像Go的接口一样)。

As for why would a language designer (such as Guido, in the case of Python) choose such "wolves in sheep clothing" as Python's ABCs, over the more straightforward Haskell-like typeclasses that I had proposed since back in 2002, that's a harder question to answer. After all, it isn't as if Python has any compunction against borrowing concepts from Haskell (e.g., list comprehensions / generator expressions -- Python needs a duality here, while Haskell doesn't, because Haskell is "lazy"). The best hypothesis I can offer is that, by now, inheritance is so familiar to most programmers that most language designers feel they can gain easier acceptance by casting things that way (although Go's designers must be commended for not doing that).

至于为什么一个语言设计师(比如Guido,在Python的情况下)选择这样的“绵羊服装狼”作为Python的ABCs,而不是我自2002年以来提出的更简单的类似Haskell的类型类,那就更难了问题要回答。毕竟,并不是说Python对Haskell的借用概念有任何反对意义(例如,列表推导/生成器表达式 - Python在这里需要二元性,而Haskell则不需要,因为Haskell是“懒惰的”)。我能提供的最好的假设是,到目前为止,大多数程序员都非常熟悉继承,以至于大多数语言设计师都认为通过这样的方式可以更容易地接受(尽管Go的设计者必须因为不这样做而受到赞扬)。

#3


1  

Let me start bold: I perfectly understand the motivation of having it and can't understand the motivation of some people to argue against it...

让我开始大胆:我完全理解拥有它的动机,并且无法理解某些人反对它的动机......

What you want is nonvirtual ad hoc polymorphism.

你想要的是非虚拟ad hoc多态。

  • ad hoc: implementation can vary
  • 临时:实施可能有所不同

  • nonvirtual: for performance reasons; compiletime dispatch
  • 非虚拟的:出于性能原因;编译时发送

The rest is sugar in my opinion.

其余的是我认为的糖。

C++ already has ad hoc polymorphism via templates. "Concepts" however would clarify what kind of ad hoc polymorphic functionality is used by which user defined entity.

C ++已经通过模板具有ad hoc多态性。然而,“概念”将阐明哪个用户定义的实体使用什么类型的特殊多态功能。

C# just doesn't have any way to do it. An approach that wouldn't be nonvirtual: If types like float would just implement something like "INumeric" or "IAddable" (...) we would at least be able to write a generic min, max, lerp and based on that clamp, maprange, bezier (...). However it wouldn't be fast. You dont want that.

C#只是没有办法做到这一点。一种不是非虚拟的方法:如果像float这样的类型只是实现“INumeric”或“IAddable”(...)之类的东西,我们至少可以编写一个通用的min,max,lerp并基于该钳位,maprange,bezier(...)。然而,它不会很快。你不想那样。

Ways of fixing this: Since .NET does JIT compilation anyway also generates different code for List<int> than for List<MyClass> (due to the differences of value and reference types) it probably wouldn't add that much of an overhead to also generate different code for the ad hoc polymorphic parts. The C# language would just need a way to express it. One way is what you sketched up.

解决这个问题的方法:由于.NET无论如何JIT编译也为List 生成不同于List 的代码(由于值和引用类型的不同),它可能不会增加大量的开销还为ad hoc多态部分生成不同的代码。 C#语言只需要一种方式来表达它。一种方法是你勾勒出来的。

Another way would be to add type constraints to the function using an ad hoc polymorphic function:

另一种方法是使用ad hoc多态函数向函数添加类型约束:

    U SuperSquare<T, U>(T a) applying{ 
         nonvirtual operator (*) T (T, T) 
         nonvirtual Foo U (T)
    }
    {
        return Foo(a * a);
    }

Of course you could end up with more and more constraints when implementing Bar that uses Foo. So you may want a mechanism to give a name to several constraints that you regularly use... However this again is sugar and one way to approach it would be to just use the typeclass concept...

当然,在实现使用Foo的Bar时,最终会遇到越来越多的限制。因此,您可能需要一种机制来为您经常使用的几个约束命名...然而,这又是糖,一种接近它的方法就是使用类型类概念......

Giving a name to several constraints is like defining a type class, but i'd like to just look at it as some sort of abbreviation mechanism - sugar for an arbitrary collection of function type constraints:

为几个约束命名就像定义一个类型类,但我想把它看作某种缩写机制 - 用于函数类型约束的任意集合的糖:

    // adhoc is like an interface: it is about collecting signatures
    // but it is not a type: it dissolves during compilation 
    adhoc AMyNeeds<T, U>
    {
         nonvirtual operator (*) T (T, T) 
         nonvirtual Foo U (T)
    } 

    U SuperSquare<T, U>(T a) applying AMyNeeds<T, U>        
    {
        return Foo(a * a);
    }

At some place "main" all the type arguments are known and everything becomes concrete and can be compiled together.

在某些地方“主”,所有类型参数都是已知的,一切都变得具体,可以一起编译。

What's missing still is the lack of creating different implementations. In the upper example we just used polymorphic functions and let everybody know...

缺少的是缺乏创建不同的实现。在上面的例子中,我们只使用了多态函数,让大家知道......

Implementation then again could follow the way of extension methods - in their ability to add functionality to any class at any point:

然后再实现可以遵循扩展方法的方式 - 在任何时候向任何类添加功能的能力:

 public static class SomeAdhocImplementations
 {
    public nonvirtual int Foo(float x)
    {
        return round(x);
    }
 }

In main you now can write:

在主要你现在可以写:

    int a = SuperSquare(3.0f); // 3.0 * 3.0 = 9.0 rounded should return 9

The compiler checks all "nonvirtual" ad hoc functions, finds both a built-in float (*) operator and a int Foo (float) and therefore is able to compile that line.

编译器检查所​​有“非虚拟”ad hoc函数,找到内置的float(*)运算符和int Foo(float),因此能够编译该行。

Ad hoc polymorphism of course comes with the downside that you have to recompile for each compile time type so that the right implementations get inserted. And probably IL doesn't support that being put into a dll. But maybe they work on it anyway...

Ad hoc多态性当然伴随着你必须为每个编译时类型重新编译的缺点,以便插入正确的实现。并且IL可能不支持将其放入dll中。但也许他们无论如何都要努力......

I see no real need for instanciation of a type class construct. If anything would fail on compilation we get the errors of the constraints or if those were boundled together with an "adhoc" codeclock the error message could get even more readable.

我认为没有真正需要类型类构造的实例化。如果编译时出现任何问题,我们会得到约束的错误,或者如果这些错误与“adhoc”代码时钟绑定在一起,则错误消息可能更具可读性。

    MyColor a = SuperSquare(3.0f); 
    // error: There are no ad hoc implementations of AMyNeeds<float, MyColor> 
    // in particular there is no implementation for MyColor Foo(float)

But of course also the instanciation of a type class / "adhoc polymorphism interface" is thinkable. The error message would then state: "The AMyNeeds constraint of SuperSquare has not been matched. AMyNeeds is available as StandardNeeds : AMyNeeds<float, int> as defined in MyStandardLib". It also would be possible to put the implementation in a class together with other methods and add the "adhoc interface" to the list of supported interfaces.

但当然,类型类/“adhoc多态性接口”的实例化也是可以想象的。然后,错误消息将声明:“SuperSquare的AMyNeeds约束尚未匹配.AMyNeeds可用作StandardNeeds:AMyNeeds ,如MyStandardLib中所定义”。也可以将实现与其他方法一起放在一个类中,并将“adhoc接口”添加到支持的接口列表中。 ,int>

But independant of the particular language design: i don't see the downside of adding them one way or the other. Save statically typed languages will always need to push the boundary of expressive power, since they started by allowing too little, which tends to be a smaller set of expressive power a normal programmer would have expected to be possible...

但独立于特定的语言设计:我没有看到以某种方式添加它们的缺点。保存静态类型语言总是需要突破表达能力的边界,因为它们通过允许太少而开始,这往往是正常程序员预期可能的一小部分表达能力......

tldr: i am on your side. Stuff like this sucks in mainstream statically typed languages. Haskell showed the way.

tldr:我在你身边。像这样的东西在主流的静态类型语言中很糟糕。哈斯克尔表明了道路。

#4


0  

What's the reasoning against typeclasses?

反对类型类的原因是什么?

Implementation complexity for compiler writers is always a concern when considering new language features. C++ already made that mistake and we've already suffered years of buggy C++ compilers as a consequence.

在考虑新的语言特性时,编译器编写器的实现复杂性始终是一个问题。 C ++已经犯了这个错误,因此我们已经遭遇了多年的错误C ++编译器。

Interfaces are inheritance-based and thus relatively slow due to indirection and moreover there is no possibility of letting an existing type implement them

接口是基于继承的,因此由于间接而相对较慢,而且不可能让现有类型实现它们

Not true. Look at OCaml's structurally-typed object system, for example:

不对。查看OCaml的结构类型对象系统,例如:

# let foo obj = obj#bar;;
val foo : < bar : 'a; .. > -> 'a = <fun>

That foo function accepts any object of any type that provides the necessary bar method.

该foo函数接受任何提供必要bar方法的任何类型的对象。

Same for ML's higher-order module system. Indeed, there is even a formal equivalence between that and type classes. In practice, type classes are better for small scale abstractions such as operator overloading whereas higher-order modules are better for large scale abstractions such as Okasaki's parameterization of catenable lists over queues.

ML的高阶模块系统也是如此。实际上,在类和类之间甚至存在形式上的等价。实际上,类型类对于小规模抽象(例如运算符重载)更好,而高阶模块更适合大规模抽象,例如Okasaki对队列中的可连接列表的参数化。

Do they have serious disadvantages after all?

毕竟他们有严重的劣势吗?

Look at your own example, generic arithmetic. F# can actually already handle that particular case thanks to the INumeric interface. The F# Matrix type even uses that approach.

看看你自己的例子,通用算术。由于INumeric接口,F#实际上已经可以处理该特定情况。 F#Matrix类型甚至使用该方法。

However, you've just replaced the machine code for add with dynamic dispatch to a separate function, making arithmetic orders of magnitude slower. For most applications, that is uselessly slow. You can solve that problem by performing whole program optimizations but that has obvious disadvantages. Moreover, there is little commonality between numerical methods for int vs float due to numerical robustness so your abstraction is also practically useless.

但是,您刚刚将用于添加动态调度的机器代码替换为单独的函数,从而使算术数量级更慢。对于大多数应用程序来说,这是无用的慢。您可以通过执行整个程序优化来解决该问题,但这有明显的缺点。此外,由于数值稳健性,int与float的数值方法之间几乎没有共性,因此您的抽象实际上也是无用的。

The question should surely be: can anyone make a compelling case for the adoption of type classes?

问题当然应该是:任何人都可以为采用类型类做出令人信服的理由吗?

#5


0  

But still no "mainstream-language" provides [type classes.]

但仍然没有“主流语言”提供[类型类]。

When this question was asked, this might have been true. Today, there is a much stronger interest in languages like Haskell and Clojure. Haskell has type classes (class / instance), Clojure 1.2+ has protocols (defprotocol / extend).

当问到这个问题时,这可能是真的。今天,人们对Haskell和Clojure等语言的兴趣更浓厚。 Haskell有类型类(类/实例),Clojure 1.2+有协议(defprotocol / extend)。

What's the reasoning against [type classes]?

反对[类型]的原因是什么?

I don't think that type classes are objectively "worse" than other polymorphism mechanisms; they just follow a different approach. So the real question is, do they fit well into a particular programming language?

我不认为类型类比其他多态机制客观地“更差”;他们只是采用不同的方法。所以真正的问题是,它们是否适合特定的编程语言?

Let's briefly consider how type classes are different from interfaces in languages such as Java or C#. In these languages, a class only supports interfaces that are explicitly mentioned and implemented in that class' definition. Type classes, however, are interfaces that can be later appended to any already defined type, even in another module. This kind of type extensibility is obviously quite different from the mechanisms in certain "mainstream" OO languages.

让我们简单地考虑一下类型类与Java或C#等语言中的接口有何不同。在这些语言中,类仅支持在该类定义中明确提及和实现的接口。但是,类型类是可以稍后附加到任何已定义类型的接口,即使在另一个模块中也是如此。这种类型的可扩展性与某些“主流”OO语言的机制明显不同。


Let's now consider type classes for a few mainstream programming languages.

现在让我们考虑一些主流编程语言的类型类。

Haskell: No need to say that this language has type classes.

Haskell:不需要说这种语言有类型类。

Clojure: As said above, Clojure has something like type classes in the form of protocols.

Clojure:如上所述,Clojure有类似于协议形式的类型。

C++: As you've said yourself, concepts were dropped from the C++11 specification.

C ++:正如您自己所说,概念已从C ++ 11规范中删除。

On the contrary: Concepts, which are a quite analogical idea, have been excluded from the next C++!

恰恰相反:Concept是一个非常类比的概念,已被排除在下一个C ++之外!

I haven't followed the whole debate around this decision. From what I've read, concepts weren't "ready yet": There was still debate about concept maps. However, concepts weren't given up completely, it is expected that they will make it into the next version of C++.

我没有围绕这个决定进行整个辩论。从我所读到的,概念还没有“准备就绪”:关于概念图仍然存在争议。但是,概念并没有完全放弃,预计它们将成为C ++的下一个版本。

C#: With language version 3, C# has essentially become a hybrid of the object-oriented and functional programming paradigms. One addition was made to the language that is conceptually quite similar to type classes: extension methods. The main difference is that you're (seemingly) attaching new methods to an existing type, not interfaces.

C#:使用语言版本3,C#基本上成为面向对象和函数式编程范例的混合体。对概念上与类型类非常相似的语言进行了一处补充:扩展方法。主要区别在于您(似乎)将新方法附加到现有类型,而不是接口。

(Granted, the extension method mechanism is not nearly as elegant as Haskell's instance … where syntax. Extensions methods are not "truly" attached to a type, they are implemented as a syntactical transformation. In the end however, this doesn't make a very big practical difference.)

(当然,扩展方法机制并不像Haskell的实例那样优雅......语法。扩展方法不是“真正”附加到类型,它们是作为语法转换实现的。但最终,这并不是很大的实际差异。)

I don't think this is going to happen anytime soon — the language designers probably won't even add extension properties to the language, and extension interfaces would even go a step further than that.

我认为这不会很快发生 - 语言设计者可能甚至不会在语言中添加扩展属性,扩展接口甚至会更进一步。

(VB.NET: Microsoft has been "co-evolving" the C# and VB.NET languages for some time, so my statements about C# happen to be valid for VB.NET, too.)

(VB.NET:微软已经将C#和VB.NET语言“共同发展”了一段时间,所以我对C#的陈述恰好也适用于VB.NET。)

Java: I don't know Java very well, but of the languages C++, C# and Java, it is probably the "purest" OO language. I don't see how type classes would fit naturally into this language.

Java:我不太了解Java,但是在C ++,C#和Java语言中,它可能是“最纯粹的”OO语言。我没有看到类型类如何适合这种语言。

F#: I've found a forum post explaining why type classes might never get introduced into F#. That explanation centers around the fact that F# has a nominative, and not a structural type system. (Though I'm not certain whether this is a sufficient reason for F# not to have type classes.)

F#:我发现了一个论坛帖子,解释了为什么类型类可能永远不会被引入F#。这种解释围绕着这样一个事实:F#具有主格,而不是结构类型系统。 (虽然我不确定这是否是F#没有类型类的充分理由。)

#6


-3  

Try define a Matroid, which is what we do (logcally and not orally saying a Matroid), and it's still likely something like a C struct. Liskov principle (latest turing medalist) gets too abstract, too categorical, too theoretic, less treating actual data and more pure theoretical class-system, for handson pragmatic problemsolving, briefly glanced it which looked like PROLOG, code about code about code about code...while an algorithm describes sequences and travelsals we understand on paper or blackboard. Depends which goal you have, solving problem with minimal code or most abstract.

尝试定义一个Matroid,这就是我们所做的(logcally而不是口头说Matroid),它仍然可能像C结构。利斯科夫原则(最新的图灵奖章获得者)过于抽象,过于绝对,理论太少,对待实际数据和更纯粹的理论类系统,对于实际操作问题的解决,简要地浏览了看起来像PROLOG的代码,关于代码的代码代码。 ..而算法描述了我们在纸上或黑板上理解的序列和旅行。取决于您拥有的目标,用最少的代码或最抽象的方式解决问题。