一個咖哩咖哩

熟悉的

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

此示例主要從此處進行調整