The familiar
curry :: ((a,b) -> c) -> a -> b -> c
curry = \f a b -> f (a,b)
function can be generalized to tuples of arbitrary arity, for example:
curry3 :: ((a, b, c) -> d) -> a -> b -> c -> d
curry4 :: ((a, b, c, d) -> e) -> a -> b -> c -> d -> e
However, writing such functions for tuples of arity 2 to (e.g.) 20 by hand would be tedious (and ignoring the fact that the presence of 20 tuples in your program almost certainly signal design issues which should be fixed with records).
We can use Template Haskell to produce such curryN
functions for arbitrary n
:
{-# LANGUAGE TemplateHaskell #-}
import Control.Monad (replicateM)
import Language.Haskell.TH (ExpQ, newName, Exp(..), Pat(..))
import Numeric.Natural (Natural)
curryN :: Natural -> Q Exp
The curryN
function takes a natural number, and produces the curry function of that arity, as a Haskell AST.
curryN n = do
f <- newName "f"
xs <- replicateM (fromIntegral n) (newName "x")
First we produces fresh type variables for each of the arguments of the function - one for the input function, and one for each of the arguments to said function.
let args = map VarP (f:xs)
The expression args
represents the pattern f x1 x2 .. xn
. Note that a pattern is seperate syntactic entity - we could take this same pattern and place it in a lambda, or a function binding, or even the LHS of a let binding (which would be an error).
ntup = TupE (map VarE xs)
The function must build the argument tuple from the sequence of arguments, which is what
we've done here. Note the distinction between pattern variables (VarP
) and expression variables (VarE
).
return $ LamE args (AppE (VarE f) ntup)
Finally, the value which we produce is the AST \f x1 x2 .. xn -> f (x1, x2, .. , xn)
.
We could have also written this function using quotations and 'lifted' constructors:
...
import Language.Haskell.TH.Lib
curryN' :: Natural -> ExpQ
curryN' n = do
f <- newName "f"
xs <- replicateM (fromIntegral n) (newName "x")
lamE (map varP (f:xs))
[| $(varE f) $(tupE (map varE xs)) |]
Note that quotations must be syntactically valid, so [| \ $(map varP (f:xs)) -> .. |]
is invalid, because there is no way in regular Haskell to declare a 'list' of patterns - the above is interpreted as \ var -> ..
and the spliced expression is expected to have type PatQ
, i.e. a single pattern, not a list of patterns.
Finally, we can load this TH function in GHCi:
>:set -XTemplateHaskell
>:t $(curryN 5)
$(curryN 5)
:: ((t1, t2, t3, t4, t5) -> t) -> t1 -> t2 -> t3 -> t4 -> t5 -> t
>$(curryN 5) (\(a,b,c,d,e) -> a+b+c+d+e) 1 2 3 4 5
15
This example is adapted primarily from here.