[Haskell-cafe] Tupling functions

Casey McCann cam at uptoisomorphism.net
Wed Sep 14 04:45:49 CEST 2011

On Tue, Sep 13, 2011 at 10:03 PM, Chris Smith <cdsmith at gmail.com> wrote:
> 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

        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

> 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

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.

More information about the Haskell-Cafe mailing list