Re: [Haskell-cafe] programming style...and type classes...

While we have stated in this thread what typeclasses should _not_ be used for, we probably do not have carved out yet what `proper' use is. Please help me here. There are some typeclasses, e.g. Eq, Ord, Num, Monoid, Functor, Monad and Category, that arise from category theory and algebra, so these may be regarded as "good" typeclasses even though a type can have several sensible instances. In that case, newtypes may express this. (Data.Monoid is full of such newtypes.) In defense of Ord, there are in general many ways to totally order a set (read "set" as "total elements of a type"), and none of these ways can be preferred a priori. I would not say this is an inherently bad property of Ord. Similarly, there are many ways to put a monoid or ring structure on a set. Sometimes a type class can be avoided. For example, there is Data.Foldable.toList. Hence, instead of writing a Foldable instance for your type and use a function from the Data.Foldable module, you could instead write your own toList conversion function and then fold that list. (Does anyone know a function involving Foldable that can not be reduced to 'toList' followed by some list operations? Is [] the "free instance" of Fodable?) However, this reduction may be much more inefficient than using the Foldable instance directly. As a contrived example of what I mean by "free instance", consider the following class that abstracts traversal over a sequence of unknown length. class ListLike l where patternmatch :: l a -> Maybe (a,l a) You can write ListLike instances for [], Vector, Sequence and Set, but only [a] is the solution to the type equation x = Maybe (a,x). Then there are multi-parameter type classes. This has been described as metaphorical to a type-level Prolog language [1], since ultimately multi-parameter type classes (without methods) are relations between types. Another debatable use case: You define many record types, each of which has a sort key component: data A = A {foo :: Foo, bar :: Bar, keyA :: Int} data B = B {baz :: Baz, keyB :: Int} data C = C {... , keyC :: Int} It might be convenient to define a typeclass class HasSortKey a where key :: a -> Int and endow the types A, B and C with the obvious instances. But one could also avoid the class by making a parametric type: data Keyed a = Keyed {payload :: a, key :: Int} and then let data A = A {foo :: Foo, bar :: Bar} and so forth. Which version would you say is `proper' use? -- Olaf [1] https://www.reddit.com/r/haskell/comments/ck459/typed_typelevel_programming_...

On Sat, Nov 5, 2016 at 11:45 AM, Olaf Klinke
While we have stated in this thread what typeclasses should _not_ be used for, we probably do not have carved out yet what `proper' use is. Please help me here.
It may be helpful to recall the original purpose of classes, which was to
allow controlled, ad-hoc overloading. Otherwise, we’d be stuck using
specialized functions for every type, passing around explicit equality
tests, or being stuck with the equivalent of deriving(Eq) on every type.
Now that we have qualified types, we can write algorithms which are generic
over class members. This, I would say, is the sign that a class is useful.
If you can’t usefully write code which is generic over a class, then you
don’t gain much from using the class. (You do gain not having to come up
with new names, though, and that’s not nothing.)
Some people claim that you shouldn’t define a class unless you can describe
some sort of algebraic laws for it, but I think that’s too strong. For one
thing, it would mean we lose equality and numeric operations for Float,
which is one of the things classes were invented for in the first place.
Instead, define a class if (1) you have a set of operations that can be
applied at multiple types but aren’t polymorphic, (2) you can give a formal
or informal description of the operations which are enough to reasonably
distinguish a good instance from a bad one, and (3) using a class makes
your code easier to understand.
--
Dave Menendez

On 06/11/16 00:06, David Menendez wrote:
Some people claim that you shouldn’t define a class unless you can describe some sort of algebraic laws for it, but I think that’s too strong. For one thing, it would mean we lose equality and numeric operations for Float, which is one of the things classes were invented for in the first place.
I've heard that too and agree with your objection. I think what underlies that critic, is that introduccion type classes without laws makes it harder to see the "intuition" of what such class ought to do (this in the context of reading, not writing). I specifically remember being a beginners and see class RegexOptions regex compOpt execOpt not understanding what really meant and why had to sprinkle it through my code. A good case of what you say is Data.Vector.Generic.Vector, (which argueably make more sense as a backpack-module) that makes the type class usage intuition clear. So in resume, what we want is "easy to understand usage" typeclasses more than laws. To that I think putting emphasis on the important instances goes great length in helping.
Instead, define a class if (1) you have a set of operations that can be applied at multiple types but aren’t polymorphic, (2) you can give a formal or informal description of the operations which are enough to reasonably distinguish a good instance from a bad one, and (3) using a class makes your code easier to understand.
(2) hits the nail, specially when reading others code! -- -- Ruben
participants (3)
-
David Menendez
-
Olaf Klinke
-
Ruben Astudillo