Improving MPTC usability when fundeps aren't appropriate?

Hi all, I've been playing with multiparameter typeclasses recently and have written a few "uncallable methods" in the process. For example, in class Moo a b where moo :: a -> a the moo function is effectively impossible to call (no amount of type annotations can tell the compiler what you intended b to be there). Some might suggest adding an a -> b functional dependency, but in some cases that is not appropriate, as there are multiple possible instances. Another solution would be to artificially force moo to take a "dummy" b so that the compiler can figure out which instance you meant. That's what I've been doing in the mean time, but wouldn't it be simpler and less hackish to add a some form of "instance annotation", like a type annotation, that would make it possible to specify what instance you wanted when it's ambiguous? I'm not sure what syntax might be appropriate here, but it could also be seen as "opening" a particular instance, so something "open"-like might be good. I don't know whether this has already been discussed much, but I was unable to find anything that seemed relevant (beyond discussing relationships between parametrized modules and typeclasses) but I wanted to know if anyone had any opinion on adding such a feature to (a future revision of) Haskell? I know people are generally reluctant to add new syntax elements to a language, but considering the lack of such a feature and the impossibility of writing such a thing in the language itself, it seems like it'd be useful to add to the language itself. Thanks, Dan

Hello Daniel, Wednesday, August 5, 2009, 8:00:06 PM, you wrote:
class Moo a b where moo :: a -> a
instances. Another solution would be to artificially force moo to take a "dummy" b so that the compiler can figure out which instance you meant. That's what I've been doing in the mean time, but wouldn't it be simpler and less hackish to add a some form of "instance annotation", like a type annotation, that would make it possible to specify what instance you wanted when it's ambiguous?
imho, no. you propose to add one more feature when existing features can serve: class Moo a b where moo :: a -> b -> a f x = moo x (undefined::Int) btw, may be associated types or associated type synonyms (these are novel features superseding FDs) is what you need? -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

Adding a dummy argument is what I've been doing so far, but it feels
hackish. Typeclasses add an implicit parameter containing the
dictionary of methods, and it seemed reasonable for me to have a more
direct influence over its value. If I must add another explicit
parameter to specify which dictionary to use, I might as well just
pass the dictionary around myself.
On Wed, Aug 5, 2009 at 12:56 PM, Bulat
Ziganshin
Hello Daniel,
Wednesday, August 5, 2009, 8:00:06 PM, you wrote:
class Moo a b where moo :: a -> a
instances. Another solution would be to artificially force moo to take a "dummy" b so that the compiler can figure out which instance you meant. That's what I've been doing in the mean time, but wouldn't it be simpler and less hackish to add a some form of "instance annotation", like a type annotation, that would make it possible to specify what instance you wanted when it's ambiguous?
imho, no. you propose to add one more feature when existing features can serve:
class Moo a b where moo :: a -> b -> a
f x = moo x (undefined::Int)
btw, may be associated types or associated type synonyms (these are novel features superseding FDs) is what you need?
-- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

Daniel Peebles wrote:
I've been playing with multiparameter typeclasses recently and have written a few "uncallable methods" in the process. For example, in
class Moo a b where moo :: a -> a
the moo function is effectively impossible to call (no amount of type annotations can tell the compiler what you intended b to be there). Some might suggest adding an a -> b functional dependency, but in some cases that is not appropriate, as there are multiple possible instances.
You can factor out moo into a type class involving only a . And if you can't do that, then you've got a problem with ambiguous instances anyway.
Another solution would be to artificially force moo to take a "dummy" b so that the compiler can figure out which instance you meant. That's what I've been doing in the mean time, but wouldn't it be simpler and less hackish to add a some form of "instance annotation", like a type annotation, that would make it possible to specify what instance you wanted when it's ambiguous? I'm not sure what syntax might be appropriate here, but it could also be seen as "opening" a particular instance, so something "open"-like might be good.
I don't think that the syntax for such a feature will be very different from a dummy argument. Also note that instead of using the actual type b as argument, as in moo :: b -> a -> a moo (undefined :: Foo) ... -- usage , you can use a phantom type data Instance a = I moo :: Instance b -> a -> a bar = I :: Instance Bar moo bar ... -- usage Regards, apfelmus -- http://apfelmus.nfshost.com

Daniel Peebles wrote:
I've been playing with multiparameter typeclasses recently and have written a few "uncallable methods" in the process. For example, in
class Moo a b where moo :: a -> a
Another solution would be to artificially force moo to take a "dummy" b so that the compiler can figure out which instance you meant. That's what I've been doing in the mean time, but wouldn't it be simpler and less hackish to add a some form of "instance annotation", like a type annotation, that would make it possible to specify what instance you wanted when it's ambiguous?
Syntax aside, dummy arguments have a disadvantage when it comes to optimizing code, because the compiler doesn't know that the dummy argument is unused in the function; indeed you could define instances where the dummy argument is used. For this reason it's technically better to use a newtype instead: newtype Lambda t a = Lambda { unLambda :: a } and, say, class Moo a b where moo :: Lambda b (a -> a) Note that we can convert between functions taking dummy arguments and such "lambda" types easily: lambdaToDummy :: Lambda t a -> t -> a lambdaToDummy a _ = unLambda a dummyToLambda :: (t -> a) -> Lambda t a dummyToLambda f = Lambda (f undefined) In fact, lambdaToDummy makes a great infix operator: (@@) :: Lambda t a -> t -> a (@@) = lambdaToDummy infixl 1 @@ Now we can write moo @@ (undefined :: x) where with dummy arguments we would write moo (undefined :: x) . The compiler will inline (@@) and lambdaToDummy (they're both small) and produce unLambda moo , that is, the plain value of type a -> a that we wanted to use in the first place. All this happens after type checking and fixing the instance of Moo to use. Bertram
participants (4)
-
Bertram Felgenhauer
-
Bulat Ziganshin
-
Daniel Peebles
-
Heinrich Apfelmus