How to best handle classes of types, interactions and listing of different types?

Hi! I'm still studying the theory of Haskell, but i also wanted to start dabbling with guideless coding, all by myself, and it happens experience is just as much important as theoretical knowledge! I'm not surprise mind you :P Say there's a class, many types that can instantiate it. Then, i in fact need to be able to concatenate (mappend would be ideal!), make lists of values of different types, all instantiating the class. I'm a bit lost on this. There are at least two sorts of ways to handle said group of types, but i'm not certain either is best, and i'm not even very much satisfied about either. Plus they don't specifically differ as regards handling of listing or otherwise interacting *between* different instances of the same class (or alternately, different types born from the same Type constructor). First idea i had, was based on making a class indeed, as well i just love this type of polymorphism (visavis other paradigms). Each type would only have to instantiate the key metshods, and no need of wrappers to use said methods, so on this respect it's pretty great. However, as soon as we want operations between values of different types, it rather gets tricky. The only way i know of to make a heterogeneous list is to use ExistentialQuantificators, create a newtype (or data?) wrapper for the class, allowing for any type to be in, without parameters. Now, the problem i have with this, is the wrapping: if i wanna make a list of types, i gotta wrap all values, and if this list becomes member of a superlist of the same polymorphic type, i gotta wrap this very sublist again with the "class wrapper". Maybe i'm imagining problems and i won't actually need to touch too much to the wrapping if i write the correct functions or instances, but from where i am, permanent wrapping seems tiresome, very much in fact removes the purpose of a class: to allow polymorphic functions without need to wrap the types of inputs/outputs inside a newtype. Which lead me to the second solution: forget the class, and make a type constructor that basically gets a list of key-methods and one field for the wrapped type. Basically it amounts to OOP, at least it seems so. I'm not really a big fan of the thing, and eventually i realized it wouldn't help me more visavis trans-type methods, especially an idea of one or more monoidal instances for the class, or the type constructor that replaces it. (Actually I'm not wholly certain as of how I'm supposed to handle Type constructors instead of classes, as I only know how to make heterogeneous out of a class, not out of a type constructor, even if its polymorphic key-methods are fields included in it. (hope i'm being clear enough, don't hesitate telling me otherwise!) So, as I'm kinda beginner as regards actual coding, i'm asking advice on the subject. Maybe the problem is in how i look at the problem, and I shouldn't even be there trying to do what i try to do. Any alternative solution or viewpoint is heartily welcome! I resume my problem once more: how to handle cross-type interactions and grouping when they only have a class in common, and if possible without having to use ExistentialQuantificators, unless there's a way to keep the wrappers far away, invisible, automatic. Thanks a lot in advance! (Ideas of specialized tutorials are also very much welcome!)

By the way if anyone knows the price on performances and any other technical or non-obvious price that one must pay while using ExistentialQuantificators, esp to make heterogeneous types then lists? On the subject of all those half-extensions half-hacks (?) i sometimes really hesitate, fearing some optimisations will suddenly disappear just because of one or two bad uses of those extensions or another. In fact with my problem i have the distinct intuition that it seems really absurd to have to use extensions of the basic haskell just to solve a situation that to me seems very possibly usual, or at least far from rare. I keep thinking I took the problem wrongly, or I'm just not seeking to do the right thing, but so far zippo...

for me a rule of thumb for choosing features / extensions is: if I see compiler errors, I should understand them and know how to fix them. Parameterized types, type sinonyms, records and classes with as few extensions as possible are pretty much all I use, type wise. You can do a lot with them alone.

On 2016-05-23 at 12:06, Silent Leaf
Say there's a class, many types that can instantiate it. Then, i in fact need to be able to concatenate (mappend would be ideal!), make lists of values of different types, all instantiating the class.
In most cases, when Haskell beginners want to make a list that contains several types from a single type class, there's a better way to organize the code. If you post your code, I'll try to suggest a specific solution. In general, try to find a simple data type that captures the same fields & functions as an unknown type that is part of the type class. Here's an example. We have a type class for vectors in a metric space, and instances for 2D, 3D, etc.
class Metric v where length :: v -> Double (*^) :: Double -> v -> v
This class has the law: s * length v == length (s *^ v) Instead of a heterogeneous list [ V2 1 2, V3 3 4 5, V4 6 7 8 9], we make a list that just has the length of each vector, and lets us multiply those lengths by a scalar. In this case, we don't even need to write a new data type, the type is simply Double. We can write:
[ length (V2 1 2), length (V3 3 4 5), length (V4 6 7 8 9) ]
And (*) gives us what Metric does with (*^). Of course, with your class, it's probably not so obvious how to transform the class this way. It's certainly possible to make a record with functions as members, moving in the object-oriented direction. Existential types (with a class constraint inside the data constructor) are even more rarely used. cheers, bergey

I understand your example. Still it's useless if we need the value of the
multiplied vectors, more than just a list of their lengths after the
operation, unless i missed something.
At any rate; I'm trying to create a representation of mathematical sets.
The tricky part being, they're precisely capable to handle any kind of
content, including themselves. But I think I went too overboard, trying to
handle the idea of sets that could contain strictly any type (at once). In
practice, the basic elements will be rather similar, and known enough at
least so that I can merely use ADTs for the various "types" of elements,
and same for sets themselves.
If needed I can create two instances of monoids, one for And, one for Or,
using newtype wrappers. It's vaguely a hassle but anyway it'll only be
useful if i have to create functions that could work on both monoids
(separately), which would be interesting enough so i don't merely duplicate
the job.
There's also the possibility i need to use existing functions already using
monoids...
For now I think i'll be ok with ADTs and one common wrapper for all
elements.
Thanks still, I'll think about your idea, it's rather interesting.
Le mardi 24 mai 2016, Daniel Bergey
On 2016-05-23 at 12:06, Silent Leaf
wrote: Say there's a class, many types that can instantiate it. Then, i in fact need to be able to concatenate (mappend would be ideal!), make lists of values of different types, all instantiating the class.
In most cases, when Haskell beginners want to make a list that contains several types from a single type class, there's a better way to organize the code. If you post your code, I'll try to suggest a specific solution.
In general, try to find a simple data type that captures the same fields & functions as an unknown type that is part of the type class. Here's an example.
We have a type class for vectors in a metric space, and instances for 2D, 3D, etc.
class Metric v where length :: v -> Double (*^) :: Double -> v -> v
This class has the law: s * length v == length (s *^ v)
Instead of a heterogeneous list [ V2 1 2, V3 3 4 5, V4 6 7 8 9], we make a list that just has the length of each vector, and lets us multiply those lengths by a scalar. In this case, we don't even need to write a new data type, the type is simply Double. We can write:
[ length (V2 1 2), length (V3 3 4 5), length (V4 6 7 8 9) ]
And (*) gives us what Metric does with (*^). Of course, with your class, it's probably not so obvious how to transform the class this way.
It's certainly possible to make a record with functions as members, moving in the object-oriented direction. Existential types (with a class constraint inside the data constructor) are even more rarely used.
cheers, bergey
participants (3)
-
Daniel Bergey
-
Imants Cekusins
-
Silent Leaf