如何正确地将编译时信息传递给Template Haskell函数?

71

我需要将编译脚本中的一些信息传递到Template Haskell中。目前,编译脚本将信息存储在系统环境中,因此我只需使用runIO包装的System.Environment.getEnvironment来读取它。是否有更好的方法,例如传递一些参数给ghc(类似于C预处理器的-D...),或者在TH中专门为此目的设计的东西?


9
从外部文件中读取信息并使用 addDependentFile 将该文件注册到 ghc --make 中是一个明显的替代方案。你对当前方案有什么问题? - Mikhail Glushenkov
2
@MikhailGlushenkov 实际上,环境只传递项目目录的根目录,然后从文件中读取更多信息。因此,addDependentFile 对我的情况很有帮助。当前方案正在运作,我只是想知道是否有其他规范的方法来做到这一点。 - Petr
5
您还可以使用location函数来获取项目目录的根目录(假设您知道从当前模块到根目录的相对路径)。这里是一个示例 - Mikhail Glushenkov
你可以在模板哈斯克尔中使用-XCPP,但似乎你的方法效果更好。 - aavogt
@aavogt 我考虑过这个问题,但我更愿意避免使用 CPP,因为我没有其他用途。 - Petr
1
你想让用户选择自己的配置文件吗?例如,通过在命令行中传递文件路径? - user3125280
2个回答

14

由于很多人对这个问题感兴趣,我会分享一下我的当前方法,希望能对某些人有所帮助。可能最好的方法是TH允许在GHC命令行上读取-D参数,但目前似乎还没有实现这样的功能。

一个简单的模块可以使TH读取编译时环境。一个辅助函数也可以读取文件;例如从环境中读取配置文件的路径,然后读取该文件。

{-# LANGUAGE TemplateHaskell #-}
module THEnv
    (
    -- * Compile-time configuration
      lookupCompileEnv
    , lookupCompileEnvExp
    , getCompileEnv
    , getCompileEnvExp
    , fileAsString
    ) where

import Control.Monad
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Language.Haskell.TH
import Language.Haskell.TH.Syntax (Lift(..))
import System.Environment (getEnvironment)

-- Functions that work with compile-time configuration

-- | Looks up a compile-time environment variable.
lookupCompileEnv :: String -> Q (Maybe String)
lookupCompileEnv key = lookup key `liftM` runIO getEnvironment

-- | Looks up a compile-time environment variable. The result is a TH
-- expression of type @Maybe String@.
lookupCompileEnvExp :: String -> Q Exp
lookupCompileEnvExp = (`sigE` [t| Maybe String |]) . lift <=< lookupCompileEnv
    -- We need to explicly type the result so that things like `print Nothing`
    -- work.

-- | Looks up an compile-time environment variable and fail, if it's not
-- present.
getCompileEnv :: String -> Q String
getCompileEnv key =
  lookupCompileEnv key >>=
  maybe (fail $ "Environment variable " ++ key ++ " not defined") return

-- | Looks up an compile-time environment variable and fail, if it's not
-- present. The result is a TH expression of type @String@.
getCompileEnvExp :: String -> Q Exp
getCompileEnvExp = lift <=< getCompileEnv

-- | Loads the content of a file as a string constant expression.
-- The given path is relative to the source directory.
fileAsString :: FilePath -> Q Exp
fileAsString = do
  -- addDependentFile path -- works only with template-haskell >= 2.7
  stringE . T.unpack . T.strip <=< runIO . T.readFile

它可以这样使用:

{-# LANGUAGE TemplateHaskell #-}
import THEnv
main = print $( lookupCompileEnvExp "DEBUG" )

接着:

  • runhaskell Main.hs 命令将会输出 Nothing
  • DEBUG="yes" runhaskell Main.hs 命令将会输出 Just "yes"

这很好,但不完美:设置环境变量不会导致 GHC 重新编译模块。是否可以在不更改 GHC 的情况下实现? - Joachim Breitner

3
看起来您正在尝试这里,ghc中的-D选项似乎定义了一个编译时变量。
在同一主题下,这个问题似乎也回答了您问题的另一部分。据我所知,要进行条件编译,您需要执行以下操作:
    #ifdef MACRO_NAME
    //Do stuff here
    #endif

1
正如我在评论中所说,我不想使用CPP和条件编译。 我没有用它,我只是想把信息传递给Template Haskell。 如果有一种不用CPP就能在TH中读取它的方法,那么“-D”选项会很好。 - Petr
1
这是关于haskell代码中的条件编译。除了使用-D定义宏(可能设置一些值),我不知道其他任何方法,然后您可以在haskell中检查该值,这可能有效。但我对haskell了解得不够确切。 - violet_white

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接