4 ms·
That's exactly it: they're a good solution to the problem, but since type classes work specifically by avoiding subtype polymorphism (and are instead a mix of p
by nrinaudo 11y ago
That's exactly it: they're a good solution to the problem, but since type classes work specifically by avoiding subtype polymorphism (and are instead a mix of parametric and ad hoc polymorphism), they cannot be part of a discussion that is centered on whether or not subtype polymorphism is a good fit.
But, yes. The more I code, the more it feels like type classes are never not a good answer.
- dllthomas 11y agoThe thing is, I don't see anything actually restricting the scope of discussion to subtype polymorphism. "The more I code, the more it feels like type classes are never not a good answer." As they are used in Haskell, they sometimes get awkward where you where multiple instances are relevant to the same data in the same scope. You can newtype cast around, but I've been wondering (with multiparameter typeclasses) if reifying the instance we care about is 1) useful, and 2) actually still distinct from explicitly passing the dictionary as a record. Regarding 1, I've consistently leaned "yes", although without tremendous confidence. Regarding 2, I've gone back and forth over time.
- nrinaudo 11y agoAs far as I'm concerned, the discussion I'm referring to started with this comment from realharo: "Usually if you need a bunch of differently-typed objects to fulfill the same contract, you can just make them implement an interface". The way I understood that is, if you have a f(a: A), and need to pass it a B and C, neither of which are subtypes of A, you can just create a new type D, have A, B and C extend D, and modify f to expect a D. My point was that it's not always practical nor even possible to do so. A type class would be a good way to abstract over the type itself and simply require a specific behaviour, but by the time you have f(a: A) where A is a concrete type, and you can't modify f (because it comes from an external library, say), it's too late to retrofit type classes into it. As for your other point - type classes being awkward when you have multiple possible instances for a given type - I agree, but must say that I'm spoiled: my primary use of type classes is with Scala, where they're just, when you come down to it, parameters to functions. You can let the compiler infer them when there is no ambiguity, but when you need to pass an int to a function that expects "A with a monoid", you always have the option of being explicit about which monoid instance to use. I believe Haskell treats them as implicit parameters as well but without the option to pass them explicitly. Finally, I'm afraid you've lost me with your last bit - reifying the instance we care about and passing the dictionary as a record. I wish I had something smart to answer, but I'm not sure I actually understand what it means. I know enough Haskell to be able to be able to read it (a necessary skill if you have even the slightest interest in FP), but not much more than that. Maybe if I knew more I'd have a clearer understanding of what you mean?
- dllthomas 11y agoFleshing out my "other point": "Passing the dictionary as a record" is a pretty common alternative to typeclasses. The way it works is if you have a typeclass: class Foo a where foo :: ... bar :: ... you instead make a record that looks like: data Foo = Foo { foo :: ..., bar :: ... } This works great for a lot of things, and is often preferable to typeclasses when all you're doing is defining an interface. One objection is that things look slightly less tidy, but the main problem with it (where it's a problem at all) is that there's no way of identifying individual implementors of an interface by type anymore. An example where this relevant is Set. We could define the Set operations to take a comparison function, but we couldn't be sure that all comparison functions agree across all interactions with a particular set. What I mean by "reifying the instance" is indexing typeclasses with a (single constructor) type that identifies the instance, and then passing that around to let us select which instance we care about, even for the same underlying type. For example: {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleInstances #-} import Data.Function (on) class ParamOrd o a | o -> a where pcompare :: o -> a -> a -> Ordering data StringsOverLength = StringsOverLength data StringsLexicographically = StringsLexicographically data IntegersInOrder = IntegersInOrder data IntegersReversed = IntegersReversed data ReversedOrdering r = ReversedOrdering r instance ParamOrd IntegersInOrder Integer where pcompare _ = compare instance ParamOrd (ReversedOrdering r) Integer where pcompare o = flip (pcompare o) instance ParamOrd StringsOverLength String where pcompare _ = compare `on` length instance ParamOrd StringsLexicographically String where pcompare _ = compare Now we could write a new version of Set, `ParamSet o a` such that we can still enforce agreement while choosing our orderings with an argument rather than a newtype.
- dllthomas 11y agoTo be clear, I'm not meaning to criticize, just understand my confusion and explore terminology. I agree with your starting point, but to my mind type classes are precisely a way to "make [types] implement an interface". I completely agree that it is often impractical to do so by defining new types. I'll address the rest when I have a little more bandwidth - I hope you'll forgive me; between interesting projects at work, a new baby, and holiday goings on, attention is in high demand.