Difference between OOP Interfaces and FP Classes

Possible duplicate:
Java interface and class like Haskell: differences and similarities?

When I started studying haskell, I was told that type classes are more powerful than / are different for interfaces.

After a year, I made extensive use of interfaces and table types, and I have yet to see an example or explanation of how they differ. This is either not a revelation that comes naturally, I missed something obvious, or in fact there is no real difference.

There is nothing significant on the Internet. So do you have an answer?

+45
oop interface functional-programming haskell typeclass
Nov 14 '11 at 13:12
source share
4 answers

You can look at it from different angles. Other people will not agree, but I think OOP interfaces are a good place to start to understand class classes (of course, compared to starting with nothing).

People like to point out that conceptually type classes classify types, like sets, - "a set of types that support these operations, as well as other expectations that cannot be encoded in the language itself." This makes sense and is sometimes done to declare a type class without methods, saying "just make your type an instance of this class if it meets certain requirements." This happens very rarely with OOP interfaces.

In terms of specific differences, there are several ways in which class classes are more powerful than OOP interfaces:

  • The biggest of these is that typeclasses separate the declaration from the fact that the type implements the interface from the declaration of the type itself. With OOP interfaces, you specify the interfaces that the type implements when you define it, and there is no way to add it later. With type classes, if you create a new type that can be implemented, but does not know about this type “up the module hierarchy”, you can write an instance declaration. If you have a type and type class from third parties that do not know about each other, you can write an instance declaration. In similar cases with OOP interfaces, you basically just get stuck, although OOP languages ​​have developed “design patterns” (adapters) to get around the limitations.

  • The next largest (this is subjective, of course) is that although conceptually OOP interfaces are a bunch of methods that can be called for objects that implement the interface, class types are a bunch of methods that can be used with types that are members class. The difference is important. Since methods of a class of a class are defined with reference to a type, not an object, there are no obstacles to using methods with several type objects as parameters (equality and comparison operators) or which return a type object as a result (various arithmetic operations) or even type constants (minimum and maximum boundaries). OOP interfaces simply cannot do this, and OOP languages ​​have developed design patterns (such as virtual cloning) to circumvent the limitation.

  • OOP interfaces can only be defined for types; type classes can also be defined for so-called "type constructors". The various types of collections defined using templates and generics in different C-based OOP languages ​​are type constructors: List takes type T as an argument and builds type List <T>. Type classes allow you to declare interfaces for type constructors: say, a mapping operation for collection types, which calls the provided function for each element of the collection, and collects the results in a new copy of the collection - potentially with a different type of element! Again, you cannot do this with OOP interfaces.

  • If a given parameter must implement several interfaces, it is trivially easy to list with class classes which of which it should be a member of; with OOP interfaces, you can specify only one interface as the type of the specified pointer or link. If you need to implement more, your only options are unattractive, such as writing one interface in a signature and casting by another, or adding separate parameters for each interface and requiring them to point to the same object. You cannot even resolve this by declaring a new empty interface that inherits from the ones you need, because the type will not be automatically considered as an implementation of your new interface just because it implements its ancestors. (If you can declare implementations after the fact, this will not be such a problem, but yes, you cannot do this either.)

  • Regarding the opposite case above, you can require that two parameters have types that implement a particular interface and that they are of the same type. With OOP interfaces, you can specify only the first part.

  • Type class instance declarations are more flexible. With OOP interfaces, you can only say: “I declare type X, and it implements the Y interface,” where X and Y are concrete. With class types, you can say "all types of lists whose element types satisfy these conditions are members of Y". (You can also say: “all types that are members of X and Y are also members of Z,” although this is problematic in Haskell for a number of reasons.)

  • The so-called "superclass restrictions" are more flexible than just inheriting an interface. With OOP interfaces, you can only say "for the type to implement this interface, it must also implement these other interfaces." The fact that the most common case with class types, as well as superclass restrictions, also allows you to say things like "SomeTypeConstructor <ThisType> must implement the so-called interface" or "results of this type applied to the type, and-so constraint", etc. .d.

  • This is currently a language extension in Haskell (like type functions), but you can declare type classes that include multiple types. For example, an isomorphism class: a class of type pairs where you can convert from one to another and back without loss. Again, it is not possible to use OOP interfaces.

  • I am sure there are more.

It is worth noting that in OOP languages ​​that add generics, some of these restrictions can be erased (fourth, fifth, possibly second point).

On the other hand, there are two essential things that OOP interfaces can do and type classes initially:

  • Dynamic submission of runtime. In OOP languages, it is trivial to bypass and store pointers to an object that implements the interface, and call methods on it at run time that will be resolved in accordance with the dynamic type of the runtime of the object. In contrast, class type restrictions are defined by default at compile time - and perhaps surprisingly, in the vast majority of cases, this is all you need. If you need dynamic dispatch, you can use the so-called existential types (which are currently a language extension in Haskell): a construct in which it "forgets" what the type of an object is and only remembers (at your discretion) that it obeyed certain class type restrictions. From this moment on, it behaves basically the same way as pointers or references to objects that implement interfaces in OOP languages, and class types have no shortage in this area. (It should be noted that if you have two existential objects that implement the same type of class and a class method of a type that requires two parameters of its type, you cannot use existences as parameters because you cannot know the existents had the same type, but compared to OOP languages ​​that cannot have such methods in the first place, this is not a loss.)

  • The runtime of objects for interfaces. In OOP languages, you can take a pointer or a link at run time and check to see if it implements an interface, and "drops" it to that interface, if so. Class types have nothing equivalent (which in some respects is an advantage because it retains a property called "parametricity," but I will not go into this here). Of course, there is nothing to prevent you from adding a new type class (or extending an existing one) using methods to expose objects of type existence to the type you want. (You can also implement this feature more universally as a library, but use it much more. I plan to finish it and upload it to Hackage someday, I promise!)

(I must point out that although you * can * do these things, many people think that OOP imitates bad style in this way and suggests that you use simpler solutions, such as explicit function entries instead of type classes. class, this option is no less powerful .)

Online OOP interfaces are typically implemented by storing a pointer or pointers in the object itself, which point to function pointer tables for the interfaces that the object implements. Type classes are usually implemented (for languages ​​that perform box polymorphism, such as Haskell, rather than polymorphism in a few attempts, such as C ++) by "passing a dictionary": the compiler implicitly passes a pointer to a table of functions (and constants) as a hidden parameter for each function that uses the type class, and the function receives one copy no matter how many objects are involved (which is why you can do what was mentioned in the second paragraph above). The implementation of existential types is very similar to what OOP languages ​​do: a pointer to a type-type dictionary is stored with the object as a "proof" that its "forgotten" type is its member.

If you've ever read about the suggestion of “concepts” for C ++ (as was originally proposed for C ++ 11), then basically Haskell-type classes are redefined for C ++ templates. I sometimes think that it would be nice to have a language that simply takes C ++ - with concepts, pulls out object-oriented and virtual functions from it, cleans up syntax and other warts and adds existential types when you need runtime dynamic dispatch over type. (Update: Rust is basically this, with many other nice things.)

+108
Nov 14 2018-11-11T00:
source share

I assume that you are talking about Haskell class types. This is actually not the difference between interfaces and class classes. As the name indicates, a type class is simply a type class with a common set of functions (and related types if you include the TypeFamilies extension).

However, a Haskell-type system in itself is more efficient than, for example, a C # -type system. This allows you to write type classes in Haskell that you cannot express in C #. Even a type class as simple as a Functor cannot be expressed in C #:

 class Functor f where fmap :: (a -> b) -> fa -> fb 

The problem with C # is that generics cannot be shared. In other words, in C # only types of the form * can be polymorphic. Haskell allows constructors of a polymorphic type, so types of any type can be polymorphic.

This is why many of the powerful common functions in Haskell ( mapM , liftA2 , etc.) cannot be expressed in most languages ​​with a less powerful type system.

+15
Nov 14 2018-11-11T00:
source share
+5
Nov 14 '11 at 2:46 a.m.
source share

The main difference — which makes class classes more flexible than interfaces — is that class types are independent of its data types and can be added later . Another difference (at least from Java) is that you can provide standard implementations. Example:

 //Java public interface HasSize { public int size(); public boolean isEmpty(); } 

Having this interface is good, but there is no way to add it to an existing class without changing it. If you're lucky, the class is not final (say ArrayList ), so you can write a subclass that implements the interface for it. If the class is final (say String ), you're out of luck.

Compare this to Haskell. You can write type-type:

 --Haskell class HasSize a where size :: a -> Int isEmpty :: a -> Bool isEmpty x = size x == 0 

And you can add existing data types to the class without touching them:

 instance HasSize [a] where size = length 

Another nice property of class types is an implicit call. For example. if you have a Comparator in Java, you need to pass it as an explicit value. In Haskell, the Ord equivalent can be used automatically as soon as the corresponding instance is in scope.

+4
Nov 14 '11 at 15:33
source share



All Articles