That is a great initiative.<br>I didn't know about those Kind extensions that enable you to pass a typeclass as a type parameter...<br><br>However, have you considered putting the Data.Exists.Default module in a separate package? That would reduce the dependencies for those who just need Exists and Existential.<br>
<br><div class="gmail_quote">2012/2/5 Gábor Lehel <span dir="ltr"><<a href="mailto:illissius@gmail.com">illissius@gmail.com</a>></span><br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
There's a common pattern in Haskell of writing:<br>
<br>
data E where E :: C a => a -> E<br>
also written<br>
data E = forall a. C a => E a<br>
<br>
I recently uploaded a package to Hackage which uses the new<br>
ConstraintKinds extension to factor this pattern out into an Exists<br>
type parameterized on the constraint, and also for an Existential type<br>
class which can encompass these kind of types:<br>
<br>
<a href="http://hackage.haskell.org/package/exists" target="_blank">http://hackage.haskell.org/package/exists</a><br>
<br>
My motivation was mostly to play with my new toys, if it turns out to<br>
be useful for anything that's a happy and unexpected bonus.<br>
<br>
Some interesting things I stumbled upon while writing it:<br>
<br>
- Did you know you can write useful existentials for Functor,<br>
Foldable, and Traversable? I sure didn't beforehand.<br>
<br>
- You can even write them for various Comonad classes, though in their<br>
case I don't think it's good for anything because you have no way to<br>
run them.<br>
<br>
- Surprisingly to me, the only * kinded class in the standardish<br>
libraries I found which is useful with existentials is Show, the * -><br>
* kinded ones are more numerous.<br>
<br>
- I don't know if anyone's ever set out what the precise requirements<br>
are for a type class method to be useful with existentials. For<br>
example, any method which requires two arguments of the same type (the<br>
type in the class head) is clearly useless, because if you have two<br>
existentials there's no way to tell whether or not their contents were<br>
of the same type. I think this holds any time you have more than one<br>
value of the type among the method's parameters in any kind of way<br>
(even if it's e.g. a single parameter that's a list). If the<br>
type-from-the-class-head (is there a word for this?) is used in the<br>
method's parameters in a position where it's not the outermost type<br>
constructor of a type (i.e. it's a type argument), that's also no<br>
good, because there's no way to extract the type from the existential,<br>
you can only extract the value. On the other hand, in the method's<br>
return type it's fine if there are multiple values of the<br>
type-from-the-class-head (or if it's used as a type argument?),<br>
because (as long as the method also has an argument of the type) the<br>
type to put into the resulting existentials can be deduced to be the<br>
same as the one that was in the argument. But if the<br>
type-from-the-class-head is used *only* in the return type, then it's<br>
difficult to construct an existential out of the return value because<br>
the instance to use will be ambiguous.<br>
<br>
- There are a lot of ways you can write existentials, and the library<br>
only captures a small part of them. Multiparameter constraint? No go.<br>
More than one constraint? No go (though you can use<br>
Control.Constraint.Combine). More than one type/value stored? No go.<br>
Anything which doesn't exactly match the patterns data E where E :: C<br>
a => a -> E or data E a where E :: C f => f a -> E a? No go. I don't<br>
think there's any way to capture all of the possibilities in a finite<br>
amount of code.<br>
<br>
- ConstraintKinds lets you write class aliases as type synonyms, type<br>
Stringy a = (Show a, Eq a). The old way to do this is class (Show a,<br>
Eq a) => Stringy a; instance (Show a, Eq a) => Stringy a and requires<br>
UndecidableInstances. But if the alias has multiple parameters, the<br>
old way is still superior, because it can be partially applied where<br>
type synonyms can't. This is analogous to the situation with type<br>
synonyms versus newtype/data declarations, but interestingly, unlike<br>
data and newtypes, the class+instance method doesn't require you to do<br>
any manual wrapping and unwrapping, only the declaration itself is<br>
different.<br>
<br>
- One of the advantages FunctionalDependencies has over TypeFamilies<br>
is that type signatures using them tend to be more readable and<br>
concise than ones which have to write out explicit equality<br>
constraints. For example, foo :: MonadState s m => s -> m () is nicer<br>
than foo :: (MonadState m, State m ~ s) => s -> m (). But with<br>
equality superclass constraints (as of GHC 7.2), it's possible to<br>
translate from TF-form to FD-form (but not the reverse, as far as I<br>
know): class (MonadStateTF m, s ~ State m) => MonadStateFDish s m;<br>
instance (MonadStateTF m, s ~ State m) => MonadStateFDish s m.<br>
<br>
- PolyKinds only seems to be useful as long as there's no value-level<br>
representation of the polykinded type involved (it's only used as a<br>
phantom). As soon as you have to write 'a' for kind * and 'f a' for<br>
kind * -> *, you have to do the duplication manually. Is this right?<br>
<br>
- Writing this library really made me want to have a type-level "Ord<br>
instance" for constraints, more precisely a type-level is-implied-by<br>
operator. The typechecker clearly knows that Eq is-implied-by Ord, for<br>
example, and that Foo is-implied-by (Foo :&: Bar), but I have no way<br>
to ask it, I can only use (~). I tried implementing this with<br>
OverlappingInstances, but it seems to be fundamentally impossible<br>
because you really need a transitive case (instance (c :<=: d, d :<=:<br>
e) => c :<=: e) but the transitive case can't work. (My best<br>
understanding is that it's because the typechecker doesn't work<br>
forward, seeing "ah, c :<=: d and d :<=: e, therefore c :<=: e";<br>
rather it works backwards, and sees that "c might be :<=: e, if<br>
there's a suitable d", but then it has no idea what to choose for d<br>
and goes into a loop.) Filing a feature request is in the plans.<br>
<br>
Er... </ul>.<br>
<br>
Cheers,<br>
~g<br>
<br>
_______________________________________________<br>
Haskell-Cafe mailing list<br>
<a href="mailto:Haskell-Cafe@haskell.org">Haskell-Cafe@haskell.org</a><br>
<a href="http://www.haskell.org/mailman/listinfo/haskell-cafe" target="_blank">http://www.haskell.org/mailman/listinfo/haskell-cafe</a><br>
</blockquote></div><br>