I was going to edit my other post, but this is big enough for its own.
Here's one way of doing it with "type magic," but it feels to me like it's somewhat suboptimal, since it requires a lifting function that is specific to functions of a particular number of arguments (more below).
Let's begin by defining a recursive datatype
data RecT a = RecR a
| RecC (a -> RecT a)
So variables of type RecT can either be just a wrapped result (RecR) or they can be a continued recursion (RecC).
Now,how do we take something and cast it into type RecT a?
Values are easy:
valRecT x = RecR x
RecR x is obviously of type RecT a.
What about a function that takes one argument, like id?
idRecT x = RecC $ \x -> RecR x
RecC wraps a function that takes a variable and returns type RecT a. The expression
\x -> RecR x
is just such a function, since as we observed before RecR x is of type RecT a.
More generally, any one-argument function can be lifted:
lift1RecT :: (a -> a) -> RecT a
lift1RecT fn = RecC $ \a -> RecR $ fn a
We can generalize this by repeatedly wrapping more deeply-nested function calls inside RecC:
lift2RecT :: (a -> a -> a) -> RecT a
lift2RecT fn = RecC $ \b -> RecC $ \a -> RecR $ fn b a
lift3RecT :: (a -> a -> a -> a) -> RecT a
lift3RecT fn = RecC $ \c -> RecC $ \b -> RecC $ \a -> RecR $ fn c b a
OK, so we've done all this work to turn a function of an arbitrary number of arguments into a single type, RecT a. How do we use this?
We can easily write down one level of function application:
reduceRecT :: RecT a -> a -> RecT a
reduceRecT (RecC fn) = fn
reduceRecT _ = undefined
In other words, reduceRecT takes an argument of type RecT a and another of type a and returns a new RecT a that's been one level reduced.
We can also unroll a finished computation inside a RecT into the result:
unrollRecT :: RecT a -> a
unrollRecT (RecR fn) = fn
unrollRecT _ = undefined
Now we're ready to apply a list of arguments to a function!
lApply :: [a] -> RecT a -> a
lApply [] fn = unrollRecT fn
lApply (l:ls) fn = lApply ls $ (reduceRecT fn) l
Let's consider the base case first: if we're finished with the computation, we just unwrap the result and return it. In the recursive case, we reduce the argument list by one, then transform the fn by applying the head of the list to the reduced fn, resulting in a new RecT a.
Let's give this a try:
lApply [2,5] $ lift2RecT (**)
> 32.0
So, advantages and disadvantages of this approach? Well, the Template Haskell version can do partial list application; this isn't true of the isorecursive type solution as presented here (though we could in principle fix this with some ugliness). The type solution also has the disadvantage of having a lot more boilerplate code associated with it: we need a listNRecT for all N that we want to use. Finally, it's a lot less easy to generalize this to the analogous tuple solution if we want to lApply to functions of mixed variable types.
Of course, another interesting possibility is to enhance brevity by using Template Haskell to generate the listNRecT functions; this eliminates some boilerplate, but in a sense buys the disadvantages of both implementations.