一个咖喱咖喱
熟悉的
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
此示例主要从此处进行调整 。