一個咖哩咖哩
熟悉的
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
此示例主要從此處進行調整 。