
Hi everyone, I have a general program design question, but I can't really think of good examples so it will be a bit vague. There was a discussion on Show not long ago which brought up the problem that there are several ways to "show" a data structure, and it depends on the context (or should I call it a use case?) which of these we actually want, e.g. human readable form, debug information, serialisation for later reading and so on, and one of the solutions was to propose a family of show functions that carry the intended use in their name. However, there are other type classes that are too general to assign such concrete uses to. For instance, if a data structure can have more than one meaningful (and useful) Functor or Monoid instance, what should one do? Should one refrain from instantiating these classes altogether and just use the names of operations directly? If one still decides to pick a certain set of operations as an instance, what are the factors that should guide this decision? What about designing libraries, how much should one prefer standard classes for their interfaces? It seems to me that there is practically no literature on design issues like these, and it would be nice to hear some opinions from experienced Haskellers. Gergely -- http://www.fastmail.fm - The way an email service should be

As for multiple Monoid or Functor instances, simply define a newtype,
as it is done in Data.Monoid.
What part of your question does that answer and what part doesn't it?
2009/1/19 Patai Gergely
Hi everyone,
I have a general program design question, but I can't really think of good examples so it will be a bit vague. There was a discussion on Show not long ago which brought up the problem that there are several ways to "show" a data structure, and it depends on the context (or should I call it a use case?) which of these we actually want, e.g. human readable form, debug information, serialisation for later reading and so on, and one of the solutions was to propose a family of show functions that carry the intended use in their name.
However, there are other type classes that are too general to assign such concrete uses to. For instance, if a data structure can have more than one meaningful (and useful) Functor or Monoid instance, what should one do? Should one refrain from instantiating these classes altogether and just use the names of operations directly? If one still decides to pick a certain set of operations as an instance, what are the factors that should guide this decision? What about designing libraries, how much should one prefer standard classes for their interfaces?
It seems to me that there is practically no literature on design issues like these, and it would be nice to hear some opinions from experienced Haskellers.
Gergely
-- http://www.fastmail.fm - The way an email service should be
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On Mon, Jan 19, 2009 at 3:58 AM, Patai Gergely
However, there are other type classes that are too general to assign such concrete uses to. For instance, if a data structure can have more than one meaningful (and useful) Functor or Monoid instance,
As a side curiosity, I would love to see an example of any data structure which has more than one Functor instance. Especially those which have more than one useful functor instance. Luke

Am Montag, 19. Januar 2009 14:31 schrieb Antoine Latter:
2009/1/19 Luke Palmer
: As a side curiosity, I would love to see an example of any data structure which has more than one Functor instance. Especially those which have more than one useful functor instance.
(,) ?
-Antoine
Wrong kind. And (,) a has only one useful instance.

Am Montag, den 19.01.2009, 14:47 +0100 schrieb Daniel Fischer:
Am Montag, 19. Januar 2009 14:31 schrieb Antoine Latter:
2009/1/19 Luke Palmer
: As a side curiosity, I would love to see an example of any data structure which has more than one Functor instance. Especially those which have more than one useful functor instance.
(,) ?
-Antoine
Wrong kind. And (,) a has only one useful instance.
What about instance Functor ((,) a) where fmap f (x,y) = (x, f y) and instance Functor (, a) where fmap f (x, y) = (f x, y) ? Of course, the latter is not legal Haskell. But if it was, then it might be useful. Is there any way to declare this Functor instance, possibly with some GHC extensions?

The desugaring of (, a) would involve some type level lambda, and
that's not something that is available (yet).
-- Lennart
On Mon, Jan 19, 2009 at 1:49 PM, Holger Siegel
Am Montag, den 19.01.2009, 14:47 +0100 schrieb Daniel Fischer:
Am Montag, 19. Januar 2009 14:31 schrieb Antoine Latter:
2009/1/19 Luke Palmer
: As a side curiosity, I would love to see an example of any data structure which has more than one Functor instance. Especially those which have more than one useful functor instance.
(,) ?
-Antoine
Wrong kind. And (,) a has only one useful instance.
What about
instance Functor ((,) a) where fmap f (x,y) = (x, f y)
and
instance Functor (, a) where fmap f (x, y) = (f x, y)
? Of course, the latter is not legal Haskell. But if it was, then it might be useful.
Is there any way to declare this Functor instance, possibly with some GHC extensions?
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

2009/1/19 Luke Palmer
On Mon, Jan 19, 2009 at 3:58 AM, Patai Gergely
wrote: However, there are other type classes that are too general to assign such concrete uses to. For instance, if a data structure can have more than one meaningful (and useful) Functor or Monoid instance,
As a side curiosity, I would love to see an example of any data structure which has more than one Functor instance. Especially those which have more than one useful functor instance. Luke
The recent, and great, blog post about moniods [1] discusses the fact that (Num a) could be one of several different monoids and how that was handled. [1] http://sigfpe.blogspot.com/2009/01/haskell-monoids-and-their-uses.html

This is one of the shortcomings of haskell not to mention other programming
languages. Mathemathicist would find it very annoying.
Instead of
instance Monoid Integer where
mappend = (+)
mempty = 0
instance Monoid Integer where
mappend = (*)
mempty = 1
which is not legal and the workaround
Num a => Monoid (Sum a)
Num a => Monoid (Product a)
wich is cumbersome
A mathematician would say something like:
instance Monoid Integer with operation + where
mappend = (+)
mempty = 0
and
instance Monoid Integer with operation * where
mappend = (*)
mempty = 1
But talking about shortcomings, personally I prefer to implement first a
form of assertion that permits the checking of the class properties
automatically for each new instance.
This is far more important in práctical terms.
2009/1/19 Thomas DuBuisson
On Mon, Jan 19, 2009 at 3:58 AM, Patai Gergely <
2009/1/19 Luke Palmer
: patai_gergely@fastmail.fm> wrote:
However, there are other type classes that are too general to assign such concrete uses to. For instance, if a data structure can have more than one meaningful (and useful) Functor or Monoid instance,
As a side curiosity, I would love to see an example of any data structure which has more than one Functor instance. Especially those which have more than one useful functor instance. Luke
The recent, and great, blog post about moniods [1] discusses the fact that (Num a) could be one of several different monoids and how that was handled.
[1] http://sigfpe.blogspot.com/2009/01/haskell-monoids-and-their-uses.html _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On Mon, 2009-01-19 at 21:18 +0100, Alberto G. Corona wrote:
This is one of the shortcomings of haskell not to mention other programming languages. Mathemathicist would find it very annoying.
Instead of
instance Monoid Integer where mappend = (+) mempty = 0
instance Monoid Integer where mappend = (*) mempty = 1
which is not legal and the workaround
Num a => Monoid (Sum a) Num a => Monoid (Product a)
wich is cumbersome A mathematician would say something like: instance Monoid Integer with operation + where mappend = (+) mempty = 0 and
instance Monoid Integer with operation * where
mappend = (*) mempty = 1
Check out the OBJ family of languages, particularly OBJ3 and (I think) Maude.
But talking about shortcomings, personally I prefer to implement first a form of assertion that permits the checking of the class properties automatically for each new instance.
This is far more important in práctical terms.
2009/1/19 Thomas DuBuisson
2009/1/19 Luke Palmer : > On Mon, Jan 19, 2009 at 3:58 AM, Patai Gergely
> wrote: >> >> However, there are other type classes that are too general to assign >> such concrete uses to. For instance, if a data structure can have more >> than one meaningful (and useful) Functor or Monoid instance, > > As a side curiosity, I would love to see an example of any data structure > which has more than one Functor instance. Especially those which have more > than one useful functor instance. > Luke The recent, and great, blog post about moniods [1] discusses the fact that (Num a) could be one of several different monoids and how that was handled.
[1] http://sigfpe.blogspot.com/2009/01/haskell-monoids-and-their-uses.html
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

I'd prefer something like Sum :: Monoid Integer Sum = Monoid {mappend = (+), mempty = 0} Prod :: Monoid Integer Prod = Monoid {mappend = (*), mempty = 1} instance Sum in [some code using mempty and mappend] On 19 Jan 2009, at 23:18, Alberto G. Corona wrote:
This is one of the shortcomings of haskell not to mention other programming languages. Mathemathicist would find it very annoying.
Instead of
instance Monoid Integer where mappend = (+) mempty = 0 instance Monoid Integer where mappend = (*) mempty = 1
which is not legal and the workaround Num a => Monoid (Sum a) Num a => Monoid (Product a)
wich is cumbersome A mathematician would say something like: instance Monoid Integer with operation + where mappend = (+) mempty = 0 and instance Monoid Integer with operation * where
mappend = (*) mempty = 1
But talking about shortcomings, personally I prefer to implement first a form of assertion that permits the checking of the class properties automatically for each new instance.
This is far more important in práctical terms.
2009/1/19 Thomas DuBuisson
2009/1/19 Luke Palmer : On Mon, Jan 19, 2009 at 3:58 AM, Patai Gergely
wrote:
However, there are other type classes that are too general to
assign
such concrete uses to. For instance, if a data structure can have more than one meaningful (and useful) Functor or Monoid instance,
As a side curiosity, I would love to see an example of any data structure which has more than one Functor instance. Especially those which have more than one useful functor instance. Luke
The recent, and great, blog post about moniods [1] discusses the fact that (Num a) could be one of several different monoids and how that was handled.
[1] http://sigfpe.blogspot.com/2009/01/haskell-monoids-and-their-uses.html _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

As a side curiosity, I would love to see an example of any data structure which has more than one Functor instance. Especially those which have more than one useful functor instance.
data Record a b = R { field1 :: a, field2 :: b } If I want to use fmap to transform either field, I have to declare the type to have the corresponding type variable at the end, i.e. choosing "Record a b" or "Record b a" is already a design decision, and it is driven by the standard Functor class in this case. I can define custom functions fmap1 and fmap2 manually, but then I don't get the advantages of overloading, like fmapping over a data structure containing my records. Now I understand that I can't get everything, and my question is mainly what to do when such a dilemma comes up. Those who have already encountered such a dilemma: how did it come up and what did you do to solve it? Gergely -- http://www.fastmail.fm - Same, same, but different...

Hello,
The multitude of newtypes in the Monoid module are a good indication
that the Monoid class is not a good fit for the class system (it is
ironic that discussing it resulted in such a huge thread recently :-).
How I'd approach the situation that you describe would depend on
the context (did I design the class, or am I just using it? am I
writing a library that is to be used by other people, or is the class
just used in an internal part of my program?, etc.) but, in general,
here are some ideas:
1. If one type can be made into an instance of a class in multipe ways
and I have no control over the class:
- I would provide non-overloaded versions for each implementation
- if there is a "natural" one (something that is quite commonly
used) I would use it for an instance
- if most uses are equally likely to be useful, then I would not
provide an instance but just use the non-overloaded functions. If I
did provide an instance, then I would be careful to document the
choice I made.
2. If I have control over the class I may consider changing it:
- Consider using a different class, that has operations that are
more specific to what I am doing (e.g., use a PrettyPrint class
instead of Show class)
- If many types are members of the same classes, then it may be
useful to combine them (i.e., add multiple methods that perform the
different operations).
I think that I have done all of the above in different situations, and
so I don't think that there is a single correct answer. I usually
avoid using the "newtype" trick as I find it inconvenient: usually
the newtype does not have the same operations as the underlying type
and so it cannot be used directly, and if you are going to wrap thing
just when you use the class methods, then you may as well use the
non-overloaded operations.
Hope that this helps,
Iavor
On Mon, Jan 19, 2009 at 9:40 AM, Patai Gergely
As a side curiosity, I would love to see an example of any data structure which has more than one Functor instance. Especially those which have more than one useful functor instance.
data Record a b = R { field1 :: a, field2 :: b }
If I want to use fmap to transform either field, I have to declare the type to have the corresponding type variable at the end, i.e. choosing "Record a b" or "Record b a" is already a design decision, and it is driven by the standard Functor class in this case. I can define custom functions fmap1 and fmap2 manually, but then I don't get the advantages of overloading, like fmapping over a data structure containing my records.
Now I understand that I can't get everything, and my question is mainly what to do when such a dilemma comes up. Those who have already encountered such a dilemma: how did it come up and what did you do to solve it?
Gergely
-- http://www.fastmail.fm - Same, same, but different...
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On Mon, 2009-01-19 at 10:59 -0800, Iavor Diatchki wrote:
Hello, The multitude of newtypes in the Monoid module are a good indication that the Monoid class is not a good fit for the class system
I would say rather that the class system is not a good fit for Monoid. Proposals for local instances, multiple instances, instance import/export control, etc. come up quite frequently on this list; the phenomena in question are not restricted to Monoid.
I usually avoid using the "newtype" trick as I find it inconvenient: usually the newtype does not have the same operations as the underlying type and so it cannot be used directly, and if you are going to wrap thing just when you use the class methods,
OTOH, I think you mean here `when you use class methods and when you use overloaded functions'. jcc

Hi,
On Mon, Jan 19, 2009 at 11:06 AM, Jonathan Cast
On Mon, 2009-01-19 at 10:59 -0800, Iavor Diatchki wrote:
Hello, The multitude of newtypes in the Monoid module are a good indication that the Monoid class is not a good fit for the class system
I would say rather that the class system is not a good fit for Monoid. Proposals for local instances, multiple instances, instance import/export control, etc. come up quite frequently on this list; the phenomena in question are not restricted to Monoid.
I disagree with you but that is a moot point because we are discussing Haskell, which does not have any of these features. Also, I find that in many situations where people want to use them, simpler solutions (like some of the ideas I mentioned in my previous post) suffice. That is not to say that we should stop trying to figure out how to improve the class system, but language changes require a lot more work than improving the design of the libraries.
I usually avoid using the "newtype" trick as I find it inconvenient: usually the newtype does not have the same operations as the underlying type and so it cannot be used directly, and if you are going to wrap thing just when you use the class methods,
OTOH, I think you mean here `when you use class methods and when you use overloaded functions'.
Sure, the point is that you are essentially adding a type annotation, which is like using a non-overloaded function. Compare, for example: "mappend add x y" and "getSum (mappend (Sum x) (Sum y))". I think that the first one is quite a bit more readable but, of course, this is somewhat subjective. -Iavor

On Mon, 2009-01-19 at 12:10 -0800, Iavor Diatchki wrote:
I usually avoid using the "newtype" trick as I find it inconvenient: usually the newtype does not have the same operations as the underlying type and so it cannot be used directly, and if you are going to wrap thing just when you use the class methods,
OTOH, I think you mean here `when you use class methods and when you use overloaded functions'.
Sure, the point is that you are essentially adding a type annotation, which is like using a non-overloaded function. Compare, for example: "mappend add x y" and "getSum (mappend (Sum x) (Sum y))". I think that the first one is quite a bit more readable but, of course, this is somewhat subjective.
Right. Of course, this issue comes up quite frequently; even sort :: Ord alpha => [alpha] -> [alpha] Needs to be specialized to non-standard Ord instances. I think that, if we're going to restrict type classes to only those cases where we never want to specialize an overloaded function to a non-standard instance, that we're going to end up with Eq, Num, and maybe Functor as classes. I'm not sure a general language mechanism is really needed just for those three. jcc

On Mon, 2009-01-19 at 12:10 -0800, Iavor Diatchki wrote:
Hi,
On Mon, Jan 19, 2009 at 11:06 AM, Jonathan Cast
wrote: On Mon, 2009-01-19 at 10:59 -0800, Iavor Diatchki wrote:
Hello, The multitude of newtypes in the Monoid module are a good indication that the Monoid class is not a good fit for the class system
I would say rather that the class system is not a good fit for Monoid. Proposals for local instances, multiple instances, instance import/export control, etc. come up quite frequently on this list; the phenomena in question are not restricted to Monoid.
I disagree with you but that is a moot point because we are discussing Haskell, which does not have any of these features. Also, I find that in many situations where people want to use them, simpler solutions (like some of the ideas I mentioned in my previous post) suffice. That is not to say that we should stop trying to figure out how to improve the class system, but language changes require a lot more work than improving the design of the libraries.
I usually avoid using the "newtype" trick as I find it inconvenient: usually the newtype does not have the same operations as the underlying type and so it cannot be used directly, and if you are going to wrap thing just when you use the class methods,
OTOH, I think you mean here `when you use class methods and when you use overloaded functions'.
Sure, the point is that you are essentially adding a type annotation, which is like using a non-overloaded function. Compare, for example: "mappend add x y" and "getSum (mappend (Sum x) (Sum y))". I think that the first one is quite a bit more readable but, of course, this is somewhat subjective.
data Iso a b = Iso { to :: a -> b, from :: b -> a } under :: Iso a b -> (b -> b) -> (a -> a) under iso = to iso ~> from iso under2 :: Iso a b -> (b -> b -> b) -> (a -> a -> a) under2 iso = to iso ~> under iso sumIso = Iso Sum getSum (+) = under2 sumIso mappend

Hi folks I have been known to venture the viewpoint that the "newtype trick" might benefit from improved library support, for example, here http://www.mail-archive.com/haskell-cafe@haskell.org/msg37213.html This is in a similar vein to Derek's approach, if accompanied by a little more grotesque whizzbangery. On 19 Jan 2009, at 21:51, Derek Elkins wrote:
On Mon, 2009-01-19 at 12:10 -0800, Iavor Diatchki wrote:
Sure, the point is that you are essentially adding a type annotation, which is like using a non-overloaded function. Compare, for example: "mappend add x y" and "getSum (mappend (Sum x) (Sum y))". I think that the first one is quite a bit more readable but, of course, this is somewhat subjective.
data Iso a b = Iso { to :: a -> b, from :: b -> a }
under :: Iso a b -> (b -> b) -> (a -> a) under iso = to iso ~> from iso
under2 :: Iso a b -> (b -> b -> b) -> (a -> a -> a) under2 iso = to iso ~> under iso
sumIso = Iso Sum getSum
(+) = under2 sumIso mappend
Perhaps it's worth trying to push in this direction, in search of a coherent kit. After all, there's a lot of structure out there. All the best Conor

Hello,
I don't mean to be negative here but I really fail to see how do any
of these ideas help the situation. (I do think it would be cool to
have a generic way to lift functions from one type to another that is
isomorphic to it). The fundamental problem is that there are multiple
functions of type Int -> Int -> Int (Int being just an example, for
the sake of concreteness), that can be the binary operation of a
monoid. Therefore, we cannot use the type system to determine how to
resolve the overloading of symbols like "mappend". Monoids are
general enough so that many types have multiple monoidal structure,
which is why I wrote that I don't think that they are good match for
the class system.
Defining a newtype and passing around isomorphisms seems more complex
to me than simply passing around the operations directly (i.e., not
using the class system). By the way, Miguel's preference can be coded
almost verbatim in ML using "local" declarations (I am referring to
the "let"-like construct that allows the definition of local values
that scope over multiple declarations) without any fancy type magic.
-Iavor
On Tue, Jan 20, 2009 at 8:42 AM, Conor McBride
Hi folks
I have been known to venture the viewpoint that the "newtype trick" might benefit from improved library support, for example, here
http://www.mail-archive.com/haskell-cafe@haskell.org/msg37213.html
This is in a similar vein to Derek's approach, if accompanied by a little more grotesque whizzbangery.
On 19 Jan 2009, at 21:51, Derek Elkins wrote:
On Mon, 2009-01-19 at 12:10 -0800, Iavor Diatchki wrote:
Sure, the point is that you are essentially adding a type annotation, which is like using a non-overloaded function. Compare, for example: "mappend add x y" and "getSum (mappend (Sum x) (Sum y))". I think that the first one is quite a bit more readable but, of course, this is somewhat subjective.
data Iso a b = Iso { to :: a -> b, from :: b -> a }
under :: Iso a b -> (b -> b) -> (a -> a) under iso = to iso ~> from iso
under2 :: Iso a b -> (b -> b -> b) -> (a -> a -> a) under2 iso = to iso ~> under iso
sumIso = Iso Sum getSum
(+) = under2 sumIso mappend
Perhaps it's worth trying to push in this direction, in search of a coherent kit.
After all, there's a lot of structure out there.
All the best
Conor
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

Hello again,
I think that I have done all of the above in different situations, and so I don't think that there is a single correct answer. I usually avoid using the "newtype" trick as I find it inconvenient: usually the newtype does not have the same operations as the underlying type and so it cannot be used directly, and if you are going to wrap thing just when you use the class methods, then you may as well use the non-overloaded operations. Yes, I had the same thought. If I have to explicitly convert between various wrappers, that's basically the same as defining separate functions from the start. The newtype trick is certainly nice when you don't want to mix the different uses on the same piece of data, but that was exactly my problem. I'd say your posts as well as wren ng thornton's answer my question as thoroughly as possible at this point, although wren's lengthy letter could probably even be extended and turned into a tutorial on the topic.
Of course type classes are not only important just for making dictionary passing implicit, but also because of their interactions, which is nicely demonstrated by the sigfpe blog entry that someone also linked from this thread. That's probably why I feel kind of uncomfortable when not taking advantage of standard classes wherever possible. Thanks for the answers, Gergely -- http://www.fastmail.fm - Access all of your messages and folders wherever you are

Luke Palmer wrote:
On Mon, Jan 19, 2009 at 3:58 AM, Patai Gergely
wrote: However, there are other type classes that are too general to assign such concrete uses to. For instance, if a data structure can have more than one meaningful (and useful) Functor or Monoid instance,
As a side curiosity, I would love to see an example of any data structure which has more than one Functor instance. Especially those which have more than one useful functor instance.
There are plenty of data structures which are multi-functors--- namely any type constructor with multiple arguments. The choice of order for arguments to the type constructor is arbitrary, so you can trivially choose the order you need for the Functor instance you want. Depending on intensional vs extensional definitions for types, some may argue that these are "different" types due to currying and kinding issues, but those concerns only highlight the limitations of not having type-level functions like `flip`, rather than having any category theoretic basis. -- Live well, ~wren

Patai Gergely wrote:
Hi everyone,
I have a general program design question, but I can't really think of good examples so it will be a bit vague. There was a discussion on Show not long ago which brought up the problem that there are several ways to "show" a data structure, and it depends on the context (or should I call it a use case?) which of these we actually want, e.g. human readable form, debug information, serialisation for later reading and so on, and one of the solutions was to propose a family of show functions that carry the intended use in their name.
However, there are other type classes that are too general to assign such concrete uses to. For instance, if a data structure can have more than one meaningful (and useful) Functor or Monoid instance, what should one do? Should one refrain from instantiating these classes altogether and just use the names of operations directly? If one still decides to pick a certain set of operations as an instance, what are the factors that should guide this decision? What about designing libraries, how much should one prefer standard classes for their interfaces?
It seems to me that there is practically no literature on design issues like these, and it would be nice to hear some opinions from experienced Haskellers.
As others have mentioned or alluded to, I think that if you're designing general functions where you anticipate running into these issues then you should avoid the instance-selection mechanism of type classes. You can still use the dictionary-passing idea of type classes, but you must pass the dictionaries explicitly. This approach is used by the generalized functions in Data.List[1], as well as many other places. Dictionary passing is, in essence, what higher-order programming is all about. Most often we deal with degenerate cases of dictionary passing where we only pass a single function (e.g. Data.List), but that's not the only way. Another place where dictionary passing is used extensively is in the category-extras package[2] where the dictionaries are called "F-(co)algebras". An F-algebra is still degenerate as dictionaries go (it's a single function :: f a -> a), but it can be insightful to consider it as a dictionary proper, for example with "folding" functions like foldr. Normally we look at the type of foldr and think of it as taking two arguments, one for each constructor of lists. Instead, we can use a case expression to pack those two arguments together into a single F-algebra, selecting which argument to return based on the constructor. And this can be generalized to the folding functions for any datatype. Thus, it's helpful to think of dictionaries as algebras (in generic and universal terms). A monoid is just one example of an object in universal algebra, it is an algebra defined by the underlying type, the operator, and the identity. Any type class is also an example of an algebra (e.g. Num defines an algebra with addition, multiplication, etc; Show is an algebra for showing). We can package any algebra up into a record and then pass that record around to the generic functions which need to use an instance of the algebra. The same idea can be used for passing around "laws" and "proofs" about data types (e.g. any function of type F(G a) -> G(F a) is a law that says we can distribute F over G). The only difference between using type classes and manually passing these records around is that Haskell has linguistic and runtime support for plucking the records out of the aether based on types. Since the compiler knows about type classes it can do smarter things for them than just passing dictionaries, so they should be used whenever reasonable. Type classes are wonderful, but they're not a silver bullet. Even when they're not reasonable, having first-class functions means we're not limited by their restrictions. [1] http://cvs.haskell.org/Hugs/pages/libraries/base/Data-List.html#22 [2] http://hackage.haskell.org/cgi-bin/hackage-scripts/package/category-extras -- Live well, ~wren
participants (15)
-
Alberto G. Corona
-
Antoine Latter
-
Conor McBride
-
Daniel Fischer
-
Derek Elkins
-
Eugene Kirpichov
-
Holger Siegel
-
Iavor Diatchki
-
Jonathan Cast
-
Lennart Augustsson
-
Luke Palmer
-
Miguel Mitrofanov
-
Patai Gergely
-
Thomas DuBuisson
-
wren ng thornton