模板Haskell阶段限制的麻烦

Dmi*_*lov 3 haskell template-haskell

我刚开始学习模板Haskell,并坚持使用拼接的简单问题.
在一个模块中,我实现了tupleN回复元组的第N个元素的函数:

tupleN :: Lift a => a -> Int -> Q Exp
tupleN a n = do
   (TupE as) <- lift a
   return $ as !! n
Run Code Online (Sandbox Code Playgroud)

在Main模块中,我有:

main :: IO ()
main = do
   let tup = (1::Int,'a',"hello")
   putStrLn $ show $(tupleN $tup 1)
Run Code Online (Sandbox Code Playgroud)

这似乎有效,但事实并非如此.编译器打印错误:

GHC stage restriction: `tup'
  is used in a top-level splice or annotation,
  and must be imported, not defined locally
In the expression: tup
In the first argument of `tupleN', namely `$tup'
In the expression: tupleN ($tup) 1
Run Code Online (Sandbox Code Playgroud)

如果我将元组描述正确放入拼接表达式,代码就会起作用:

main :: IO ()
main = do
   putStrLn $ show $(tupleN (1::Int,'a',"hello") 1)
Run Code Online (Sandbox Code Playgroud)

第一个变种我错过了什么?

Ben*_*ood 5

你试图tup用作拼接,但tup它只是一个普通的值.你不想用它作为前缀$.

此外,正如编译错误所述,由于模板Haskell在编译过程中运行,GHC在完成编译当前模块之前确实需要知道它正在做什么.这意味着你的splice表达式不能依赖tup,因为它仍在编译中.在拼接内部,你只能使用文字,导入的值,以及特殊'name''TypeName形式(我认为你可以将其视为一种文字).您可以通过使用eg来获取此编译中的一些信息reify,但即使这样也只能为您提供在编译时可用的数据 - 如果您想要一个可以传递用户输入的函数,或者是从用户输入构造的数据,那么只是不可能.

简而言之,您无法使用Template Haskell完成您想要做的事情.但是,您可以定义一个扩展为函数的拼接,以获取大小元组的i第th个元素sz:

import Control.Monad (unless)
import Language.Haskell.TH

tupleN :: Int -> Int -> Q Exp
tupleN sz i = do
  unless (i < sz) . reportError $ "tupleN: index " ++ show i
    ++ " out of bounds for " ++ show sz ++ "-tuple"
  lamE
    [tupP (replicate i wildP
      ++ [varP (mkName "x")]
      ++ replicate (sz - i - 1) wildP)]
    (varE (mkName "x"))
Run Code Online (Sandbox Code Playgroud)