25

The uncurry function only works for functions taking two arguments:

uncurry :: (a -> b -> c) -> (a, b) -> c

If I want to uncurry functions with an arbitrary number of arguments, I could just write separate functions:

uncurry2 f (a, b)          = f a b
uncurry3 f (a, b, c)       = f a b c
uncurry4 f (a, b, c, d)    = f a b c d
uncurry5 f (a, b, c, d, e) = f a b c d e

But this gets tedious quickly. Is there any way to generalize this, so I only have to write one function?

fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • Not in that way, but in any case, why do you want to do this? In my experience there are very few cases where you need any more than uncurry2 – Paul Johnson Aug 28 '11 at 12:31
  • 7
    @Paul: No specific reason, but as soon as I see any kind of repeated pattern, I ask myself how I can generalize and abstract from it. – fredoverflow Aug 28 '11 at 12:33

3 Answers3

23

Try uncurryN from the tuple package. Like all forms of overloading, it's implemented using type classes. In this case by manually spelling out the instances up to 15-tuples, which should be more than enough.

Variadic functions are also possible using type classes. One example of this is Text.Printf. In this case, it's done by structural induction on the function type. Simplified, it works like this:

class Foo t

instance Foo (IO a)
instance Foo b => Foo (a -> b)

foo :: Foo

It shouldn't be hard to see that foo can be instantiated to the types IO a, a -> IO b, a -> b -> IO c and so on. QuickCheck also uses this technique.

Structural induction won't work on tuples, though, as an n-tuple is completely unrelated to a n+1-tuple, so that's why the instances have to be spelled out manually.

hammar
  • 138,522
  • 17
  • 304
  • 385
  • 6
    Amusingly, the source for `Data.Tuple.Curry` mentions that the different instances were actually generated by a separate program, and then presumably pasted in as though they had been typed out by hand. – Stuart Cook Aug 28 '11 at 12:37
11

Finding ways to fake this sort of thing using overwrought type system tricks is one of my hobbies, so trust me when I say that the result is pretty ugly. In particular, note that tuples aren't defined recursively, so there's no real way to abstract over them directly; as far as Haskell's type system is concerned, every tuple size is completely distinct.

Any viable approach for working with tuples directly will therefore require code generation--either using TH, or an external tool as with the tuple package.

To fake it without using generated code, you have to first resort to using recursive definitions--typically right-nested pairs with a "nil" value to mark the end, either (,) and () or something equivalent to them. You may notice that this is similar to the definition of lists in terms of (:) and []--and in fact, recursively defined faux-tuples of this sort can be seen as either type-level data structures (a list of types) or as heterogeneous lists (e.g., HList works this way).

The downsides include, but are not limited to, the fact that actually using things built this way can be more awkward than it's worth, the code to implement the type system tricks is usually baffling and completely non-portable, and the end result is not necessarily equivalent anyway--there are multiple nontrivial differences between (a, (b, (c, ()))) and (a, b, c), for instance.

If you want to see how horrible it becomes you can look at the stuff I have on GitHub, particularly the bits here.

C. A. McCann
  • 76,893
  • 19
  • 209
  • 302
3

There is no straightforward way to write a single definition of uncurry that will work for different numbers of arguments.

However, it is possible to use Template Haskell to generate the many different variants that you would otherwise have to write by hand.

Stuart Cook
  • 3,994
  • 25
  • 23
  • You can write an n-ary `uncurry` with Daniel Fridlender and Mia Indrika's pattern for arity family functions. The example of `zipWithN` is given in the paper "Do we need Dependent Types?". – stephen tetley Aug 28 '11 at 19:42
  • “No way” was perhaps a bit bold. I've changed my answer to “no straightforward way”. – Stuart Cook Aug 28 '11 at 22:00
  • @stephentetley Has that implementation been implemented in package anywhere? – CMCDragonkai Jul 30 '15 at 05:15