一个咖喱咖喱

熟悉的

curry :: ((a,b) -> c) -> a -> b -> c
curry = \f a b -> f (a,b)

函数可以推广到任意 arity 的元组,例如:

curry3 :: ((a, b, c) -> d) -> a -> b -> c -> d
curry4 :: ((a, b, c, d) -> e) -> a -> b -> c -> d -> e 

然而,手动编写 arity 2 元组到(例如)20 的这些函数将是繁琐的(并且忽略了这样一个事实,即程序中存在 20 个元组几乎肯定会发出应该用记录修复的设计问题)。

我们可以使用 Template Haskell 为任意 n 生成这样的 curryN 函数:

{-# LANGUAGE TemplateHaskell #-}
import Control.Monad (replicateM) 
import Language.Haskell.TH (ExpQ, newName, Exp(..), Pat(..))
import Numeric.Natural (Natural) 

curryN::Natural -> Q Exp

curryN 函数采用自然数,并产生该 arity 的 curry 函数,作为 Haskell AST。

curryN n = do
  f  <- newName "f"
  xs <- replicateM (fromIntegral n) (newName "x")

首先,我们为函数的每个参数生成新的类型变量 - 一个用于输入函数,一个用于所述函数的每个参数。

  let args = map VarP (f:xs)

表达式 args 代表 f x1 x2 .. xn 的模式。请注意,模式是单独的语法实体 - 我们可以采用相同的模式并将其放在 lambda 或函数绑定中,甚至是 let 绑定的 LHS(这将是一个错误)。

      ntup = TupE (map VarE xs)

该函数必须从参数序列构建参数元组,这就是我们在这里所做的。注意模式变量(VarP)和表达式变量(VarE)之间的区别。

  return $ LamE args (AppE (VarE f) ntup)

最后,我们生成的值是 AST \f x1 x2 .. xn -> f (x1, x2, .. , xn)

我们也可以使用引号和’提升’构造函数编写此函数:

...
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)) |]

请注意,引用必须在语法上有效,因此 [| \ $(map varP (f:xs)) -> .. |] 无效,因为常规 Haskell 无法声明模式的列表 - 上面的解释为\ var -> ..,拼接表达式的类型为 PatQ,即单个模式,而不是模式列表。

最后,我们可以在 GHCi 中加载这个 TH 函数:

>: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

此示例主要从此处进行调整