several questions: multi-param typeclasses, etc.

I'm trying to understand multi-param typeclasses; a particular one (MonadError); and functional dependencies. For example, throwError in in the MonadError class definition: class Monad m => MonadError e m | m -> e where throwError :: e -> m a The concept of a multi-parameter class is a litte tricky. It's no longer so simple to say that "ONE type is AN instance of ONE class." Instead, it seems more appropriate to say that "in a context where e and m fulfill the criteria for class MonadError, the function throwError is available." In a sense, there is no single type that is an instance of MonadError. Is this the right way to put it? So I went looking for an instance of MonadError, in particular with use with Either. I couldn't find actual source code with an instance, but the Haddock documentation lists this: Instances: Error e => MonadError e (Either e) The code doesn't give the definition, but I suppose it would be: throwError e = Left e ??? Now I'm interested in understanding this functional dependency between m and e. For the compiler to decide that a particular instance's definition of throwError is available, it must decide that e is of class Error (it is given by class constraint) m is a Monad (Either e is such) and then this m -> e thing: I don't know how to put this into words Any explanation/clarification/correction appreciated. Thanks, Mike

Hi Michael, On Sun, Nov 01, 2009 at 07:15:17AM -0800, Michael Mossey wrote:
I'm trying to understand multi-param typeclasses; a particular one (MonadError); and functional dependencies.
For example, throwError in in the MonadError class definition:
class Monad m => MonadError e m | m -> e where throwError :: e -> m a
In a sense, there is no single type that is an instance of MonadError.
Is this the right way to put it?
I think a better way to put it is that an instance of MonadError is a PAIR of types, instead of a single type.
Error e => MonadError e (Either e)
The code doesn't give the definition, but I suppose it would be:
throwError e = Left e ???
Right. (No pun intended ;)
Now I'm interested in understanding this functional dependency between m and e. For the compiler to decide that a particular instance's definition of throwError is available, it must decide that
e is of class Error (it is given by class constraint) m is a Monad (Either e is such) and then this m -> e thing: I don't know how to put this into words
The m -> e thing isn't a constraint that needs to be satisfied; it gives some extra information to help the compiler with inferring which instance to use. In particular, "m -> e" says "the type chosen for m DETERMINES the type chosen for e"; put another way, "there cannot be two instances with the same type for m but different types for e". So in this case you could not also make an instance MonadError String (Either e). -Brent

Brent Yorgey wrote:
The m -> e thing isn't a constraint that needs to be satisfied; it gives some extra information to help the compiler with inferring which instance to use. In particular, "m -> e" says "the type chosen for m DETERMINES the type chosen for e"; put another way, "there cannot be two instances with the same type for m but different types for e". So in this case you could not also make an instance
MonadError String (Either e).
Thanks, Brent. Now what I'm a bit confused about: if you wrote instance (Error e) => MonadError e (Either e) and no other instance with Either e, then the compiler would have only one choice. So why would it need the extra information in the functional dependency? On the other hand, if you added instance (Error e) => MonadError String (Either e) and didn't include the functional dependency, the compiler would still run into a problem with overlapping instances and have no way to decide, which I presume is still an error. So it looks to me (no doubt because I don't understand correctly) that the functional dependency doesn't add any information or clarify any situation. Please explain! Thanks, Mike

On Sun, Nov 01, 2009 at 08:32:34AM -0800, Michael Mossey wrote:
Brent Yorgey wrote:
The m -> e thing isn't a constraint that needs to be satisfied; it gives some extra information to help the compiler with inferring which instance to use. In particular, "m -> e" says "the type chosen for m DETERMINES the type chosen for e"; put another way, "there cannot be two instances with the same type for m but different types for e". So in this case you could not also make an instance
MonadError String (Either e).
Thanks, Brent. Now what I'm a bit confused about: if you wrote
instance (Error e) => MonadError e (Either e)
and no other instance with Either e, then the compiler would have only one choice. So why would it need the extra information in the functional dependency?
Because type classes are open: when compiling a module, the compiler is never allowed to assume that the instances it sees are the only instances, because there could always be another instance declared in a module it hasn't seen yet.
On the other hand, if you added
instance (Error e) => MonadError String (Either e)
and didn't include the functional dependency, the compiler would still run into a problem with overlapping instances and have no way to decide, which I presume is still an error.
True. In the case of this particular *instance*, the functional dependency doesn't really add all that much. But that doesn't mean the functional dependency on the *class* is useless; there can be other instances of the class as well. -Brent

On Sun, Nov 1, 2009 at 5:32 PM, Michael Mossey
On the other hand, if you added
instance (Error e) => MonadError String (Either e)
and didn't include the functional dependency, the compiler would still run into a problem with overlapping instances and have no way to decide, which I presume is still an error.
Right, in this case it is true, but supposing the MonadError instance for Either was rather :
instance (Error e) => MonadError (Maybe String) (Either e)
There would be nothing a priori that would prevent you from writing another instance :
instance (Error e) => MonadError String (Either e)
There are a certain number of case where this functional constraint is thus useful. -- Jedaï

Chaddaï Fouché wrote:
On Sun, Nov 1, 2009 at 5:32 PM, Michael Mossey
wrote: On the other hand, if you added
instance (Error e) => MonadError String (Either e)
and didn't include the functional dependency, the compiler would still run into a problem with overlapping instances and have no way to decide, which I presume is still an error.
Right, in this case it is true, but supposing the MonadError instance for Either was rather :
instance (Error e) => MonadError (Maybe String) (Either e)
There would be nothing a priori that would prevent you from writing another instance :
instance (Error e) => MonadError String (Either e)
I think I understand that you are saying it would create a messy situation to write this second instance, and the functional constraint causes the compiler to stop with an error if it sees that second instance. Is that true? Now I have a question that probably doesn't make sense, but what I'm really doing is picking your brain. I want to see how you would rephrase it: Let's assume for a moment that two different people are involved in this code. One of them writes the class definition. Another one writes an instance. In OO, which I am more familiar with, one person will write a class with a limited API in order to help put guarantees on the correct behavior of the class. In a sense, that person is saying: "I release my class to the world to do what it will, but before doing that I put some constraints on it so no one can distort my intentions." Is this functional dependency a similar situation? Does it make sense from the "point of view" of the author of the class definition? Or is it more a practical necessity? Thanks, Mike

On Sun, Nov 1, 2009 at 8:31 PM, Michael Mossey
In OO, which I am more familiar with, one person will write a class with a limited API in order to help put guarantees on the correct behavior of the class. In a sense, that person is saying: "I release my class to the world to do what it will, but before doing that I put some constraints on it so no one can distort my intentions."
Is this functional dependency a similar situation? Does it make sense from the "point of view" of the author of the class definition?
Or is it more a practical necessity?
class Collection collection elt | collection -> elt where add :: elt -> collection -> collection merge :: collection -> collection -> collection ... (merge is the function that makes the functional constraint a
In this particular case of MonadError, I think the constraint is more of the first variety but most functional constraints are more practical than moral... The case in point being when you want to put a method in the class that don't include all the type parameters of the class in its type : you NEED to have functional constraints for Haskell to accept that the correct method can be determined with this partial information (and determine it later on). It is worth noting that many case where multi-param classes are used with a mandatory practical functional constraint are better expressed by the new indexed type family extension and a single param type class (though this is not always the case) : For instance the classic Collection type class : practical necessity) Can be better (or at least more cleanly, depending on who you ask) expressed as :
class Collection c where type Elt c :: * add :: Elt c -> c -> c merge :: c -> c -> c ...
-- Jedaï

So if I understand correctly, when the compiler sees this class definition:
For example, throwError in in the MonadError class definition:
class Monad m => MonadError e m | m -> e where throwError :: e -> m a
Then it sees this instance:
Instances:
Error e => MonadError e (Either e)
Because (Either e) is a monad, it "fits with" the m in the class definition. (The m has the constraint Monad on it.) Knowing that, the compiler uses the functional dependency to conclude that the given instance is the only possible instance. Is that right? Another question: in Control.Monad.Error, I see the instance MonadError IOError IO which leads to the question: apparently I can throw errors in the IO monad using throwError. But there is also throw and throwIO. What is the difference? Thanks, Mike
participants (3)
-
Brent Yorgey
-
Chaddaï Fouché
-
Michael Mossey