
On Tue, Sep 13, 2011 at 10:03 PM, Chris Smith
Ah, okay... then sure, you can do this:
class Tuple a b c | a b -> c where tuple :: a -> b -> c
instance Tuple (a -> b, a -> c) a (b,c) where tuple (f,g) x = (f x, g x)
This wouldn't actually work well in practice. There's no dependency between the various occurrences of "a" in the types, so unless they're already known to be the same, GHC will complain about an ambiguous instance (please excuse the silly GHCi prompt): Ok, modules loaded: Tupling. ∀x. x ⊢ tuple ((+3), show) 4 <interactive>:0:1: No instance for (Tuple (a0 -> a0, a1 -> String) b0 c0) arising from a use of `tuple' Given that the class is only intended to be used where those types are equal, you really want it to unify them based on use of the tuple function.
and so on... You'll need fundeps (or type families if you prefer to write it that way), and probably at least flexible and/or overlapping instances, too, but of course GHC will tell you about those.
I rather prefer type families in this case, both because the problem is easily expressed in "type function" style, and because it gives you an easy type equality constraint to use, rather than using arcane trickery with overlaps to force post-hoc unification. We'd probably want to do something like this: class Tuple t where type Arg t :: * type Result t :: * tuple :: t -> Arg t -> Result t instance (x1 ~ x2) => Tuple (x1 -> a, x2 -> b) where type Arg (x1 -> a, x2 -> b) = x1 type Result (x1 -> a, x2 -> b) = (a, b) tuple (f, g) x = (f x, g x) instance (x1 ~ x2, x2 ~ x3) => Tuple (x1 -> a, x2 -> b, x3 -> c) where type Arg (x1 -> a, x2 -> b, x3 -> c) = x1 type Result (x1 -> a, x2 -> b, x3 -> c) = (a, b, c) tuple (f, g, h) x = (f x, g x, h x) Used like so: Ok, modules loaded: Tupling. ∀x. x ⊢ tuple ((+2), show, (< 2)) 3 (5,"3",False) Note that not only does this avoid ambiguity, it even unifies ambiguous types that are then defaulted by the usual means. That said, I question the utility of a class like this. The boilerplate instances are tedious to write and it's not flexible in any way; tuples not being defined inductively makes them a real pain to work with unless there's a particularly good reason to do so. Something equivalent to right-nested (,) with () as a terminator is much more pleasant, and since we're deep in the pits of non-portability anyway, might as well pull out bang patterns and UNPACK pragmas if avoiding extra bottoms was the reason for using plain tuples. - C.