10 ms·
Haskell Typeclasses vs. C++ Classes
- mbrock 9y agoVery detailed and informative! For onlookers, I'd note that Haskell typeclasses are something of a trap for beginners who think they are the way to accomplish object-like coding in Haskell. Actually, records with functions (closures) inside is usually a better choice: simpler, more flexible, more straightforward. Of course the resemblance between a vtable and a record of functions is very obvious!
- seanmcdirmid 9y agoRecords with adhoc v-tables was how we did it in C. It was incredibly useful given an existing object schema to model.
- jonahx 9y ago> they are the way to accomplish object-like coding in Haskell. > Actually, records with functions (closures) inside is usually a better choice: The better choice would be to use Haskell as it was intended to be used.
- mbrock 9y agoAnd that doesn't include first class functions inside of data structures? :)
- jonahx 9y agoI was just responding to "object-like coding," which i took to mean "using the OO paradigm in haskell." sorry if that's not what you meant.
- Gaelan 9y agoI assume “object-like coding” just means “a data structure and a set of functions for interacting with it. Are you implying that the “right way” of using Haskell doesn’t do anything like that?
- evincarofautumn 9y agoYup, generally in terms of program structure, when I would make a class in C++/C#/Java, I make a module in Haskell. There’s usually a primary type and some helper types exported, along with functions that form the public interface. If you need polymorphism you can mostly just reach for parametric polymorphism (generics) and closures. Existential types, in the form of closures, explicit existentially quantified types, or rank-2 universally quantified functions, give you basically the same expressive power as virtual functions—they’re all encodings of the same concept. Typeclasses are more about the principle of “code to the interface, not the implementation”. For example, using a container through its Functor/Applicative/Monad/Monoid/Traversable/Foldable instances lets you be agnostic to its representation, swap it out for testing/performance, &c.
- cies 9y ago> when I would make a class in C++/C#/Java, I make a module in Haskell. This. Group you code logically, expose only what's needed. This also gives the FPer a large part of the "encapsulation" and "group by responsibility" advantages that are often mentioned as pros of OO. Without going down the "implicit state sharing" path.
- catnaroek 9y agoEncapsulation works even better with modules than with objects: (0) Modules may expose n-ary operations on abstract types for n > 1. (1) Modules may have multiple interrelated abstract type components, i.e., what Stepanov calls “multisorted algebras”. Furthermore, if you are willing to use an impure language (like ML or Scala), modules can be just as stateful as objects if you deem it necessary.
- zzalpha 9y agoI kinda wish the Haskell designers hadn't gone with the term "typeclass", as it's incredibly misleading. Typeclasses have a lot more cognitive similarity to Go-style interfaces or Smalltalk protocols than they do to OOP-style classes, and that fact can be enormously confusing to newcomers (which, at times, seems to be the Haskell raison d'etre).
- grabcocque 9y agoQuite. Typeclasses are Haskell's way of doing ad hoc polymorphism. C++ has ad hoc polymorphism too, but it's via templates not classes.
- Veedrac 9y agoTypeclasses also do virtual dispatch, though. That comparison isn't much closer.
- gpderetta 9y agoNot an Haskell expert (or even a beginner really, just generally interested in language design), but as far as I understand in 99% of the cases virtual dispatch in Haskell is purely an implementation detail: i.e. type classes can be implemented via monomorphization exactly like C++ templates (and in fact the compiler will do exactly that when optimising or when explicitly directed to do so); it isn't done by default mostly to preserve separate compilation and code explosion. There are cases where that can't be done at all (polymorphic recursion, when the recursion can't be proven to terminate at compile time, I'm sure there are other) and runtime dispatching is required, but that's more of an exception. In C++ that's implemented with explicit, semi manually implemented, type erasure wrappers. edit: as I said, I'm not an Haskell programmer; maybe in idiomatic Haskell the cases where runtime dispatch is required are much more common.
- Veedrac 9y agoImagine you have a problem for which you need virtual dispatch. In C++, you typically reach for inheritance. In Haskell, you typically reach for typeclasses. Comparatively, imagine you have a problem for which you need ad-hoc, but statically resolvable, overloads. In C++, you typically reach for templates. In Haskell, you instead typically reach again for typeclasses. This is what I mean when I say neither comparison is really closer than the other. Haskell couldn't replace typeclasses with templates, just as it couldn't replace them with objects.
- wyc 9y agoIt may even be fair to say that Haskell's typeclasses make it more like an object-oriented programming language. They have things like properties and multiple inheritance. Even Wadler has a section leading with "Object-oriented programming." in his paper on parametric polymorphism: https://people.csail.mit.edu/dnj/teaching/6898/papers/wadler88.pdf https://people.csail.mit.edu/dnj/teaching/6898/papers/wadler... (page 3)
- cies 9y agoObject is a sufficiently generic term, so that it can mean very different things to different people. Lumping state with some functions that implicitly have R/W access to that state is not possible in Haskell, not even with the powerful type-classes. Thankfully. Type-classes do allow interesting polymorphism tricks, that —as Wadler mentioned— allows Haskellers to use some of good parts of OO.
- wyc 9y agoYou're right; I still struggle to come up with a good catch-all definition for "object." On an unrelated note, I'm also still trying to figure out what DevOps is, aside from YAML and tears.
- cies 9y agoDevOps provisioning by software (hence the "dev") instead of by hand (manual-Ops). It is with cloud-native that it gets interesting. Cloud-native infrastructure (like k8s) usually replaces the traditional devops tools (like Puppet, Ansible and Salt). Also it brings the notion of "scrappable instances", "blue green deployments" and "stateless compute nodes". This is were it got really interesting, this is were the FP intuitions start carry over. :)
- wyc 9y agoInteresting last point! It does seem to be the trend, notably with large data sets: https://wycd.net/posts/2017-04-09-rpcs-for-moving-computation.html https://wycd.net/posts/2017-04-09-rpcs-for-moving-computatio...
- gpderetta 9y agoThe closest thing to Typeclasses in C++ are concepts, i.e. the set of semantic and syntactic requirements on template parameters. As of C++17, concepts are not yet an actual language construct but are enforced by convention and normally only checked at template instantiation time (as opposed to definition time), although there are techniques that can be used to simulate more formal checking (archetypes, enableif, etc.).
- bagswatchesus 9y agoThat the term "class" was already in use, and that overloading it would be confusing. http://www.bagswatchesus.com http://www.bagswatchesus.com
- timthelion 9y agoTo me, type-classes are used like a less flexible form of duck typing. I would really like to see a more flexible strongly typed duck-typing than type-classes. I think that a sufficiently advanced IDE could auto-generate, from an expression, a "duck-type" for each untyped object used. For example, if you have the expression: (given) g :: Int -> String function a = let l = (f a) in g l Would it be possible to generate the duct-type "function :: (a which has a method f :: a -> Int) -> String" Now you're not saying that a has to belong to a typeclass, you are saying that a has a duck-type of any value with a method f :: a -> Int.
- catnaroek 9y agoFine. Now propose a concrete type-checking algorithm. And make it scalable.
- wtetzner 9y agoI think row types are probably what OP is looking for. And they can be fully inferred.
- catnaroek 9y agoI don't think row polymorphism is what the OP asked for. They might be what the OP wants, but that's a-whole-nother matter. If I understood correctly, the OP asked for a “duck type of all values on which a function `f` can be applied”. Alas, OCaml-like object types are glorified function types, not duck types: (0) The type of an object is the type of its methods. (1) The type of an object is not the type of its internal state. Say you have a particular data type like `float list`, and you want to “bless it with a method `f`”, so that you can pass a `float list` to any context that expects an object with a method `f`. This is everyday stuff in duck-typed languages. Turns out, you can't. The best thing you can do is explicitly construct an object with a method `f`, and whose state happens to be a `float list`. Alas, because the state isn't a part of the object's type, there is no way a user of the object can recover the original `float list`. In fact, no user of the object has any way to tell whether the object's internal state is a `float list` or something else altogether.