如何在LTS Haskell 20.20(GHC 9.2.7)中动态编译导入模块?
动态编译并调用Haskell模块的问题
需求
实现一个Haskell程序,能够动态生成Haskell模块(.hs文件),编译后将其导入到当前应用中并调用其中的函数。
用户现有代码
Main.hs
import qualified GhcApiWrap as Ghw main :: IO () main = Ghw.msLoadModuleAndExecute "../dyn/" "DynExample.hs" "nFromChar" 'A'
GhcApiWrap.hs
module GhcApiWrap ( msLoadModuleAndExecute ) where import GHC --import GHC.Paths (libdir) msLoadModuleAndExecute :: String -> String -> String -> Char -> IO () msLoadModuleAndExecute _ _ _ _ = do value' <- runGhc (Just "./src/") $ do dflags <- getSessionDynFlags setSessionDynFlags $ dflags { ghcLink = LinkInMemory, ghcMode = CompManager, objectDir = Just "../dyn/", hiDir = Just "../dyn/" } target <- guessTarget ("DynExample.hs") Nothing setTargets [target] ret <- load LoadAllTargets case ret of Succeeded -> do importDecl_RdrName <- parseImportDecl $ "import DynExample" setContext [IIDecl importDecl_RdrName] value <- dynCompileExpr ("DynExample.nFromChar") return value _ -> return undefined print $ value'
DynExample.hs
module DynExample ( nFromChar ) where nFromChar :: Char -> Int nFromChar _ = 33
运行错误
代码可编译但无法正常运行,输出错误信息:
ExprmntGhcApi-exe: Missing file: src/settings
用户注释掉import GHC.Paths (libdir)是因为该模块在当前环境中不存在,尝试用(Just "./src/")替代(Just libdir),但多个网上流传的示例代码在GHC 9.2.7中均无法正常工作。
核心问题
在Stack环境(解析器对应LTS 20.20,即GHC 9.2.7)中,能否通过上述方式动态调用DynExample.nFromChar?如果可行,如何实现类型为msLoadModuleAndExecute :: String -> String -> String -> Char -> IO Int的函数?
环境配置
当前使用Stack,resolver配置如下:
resolver: url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/20.yaml
对应GHC版本为9.2.7。
解决方法
1. 修复libdir获取问题
GHC 9.x版本中GHC.Paths模块不再默认提供,需要手动添加ghc-paths包到项目依赖。在项目的package.yaml或.cabal文件中添加:
dependencies: - base >= 4.16 && < 5 - ghc - ghc-paths
执行stack build安装依赖后,恢复import GHC.Paths (libdir),并将runGhc的参数改为(Just libdir)——这里的libdir是GHC的系统库目录,而非项目的src目录,GHC需要它来加载自身的配置文件。
2. 调整动态编译与类型转换逻辑
修改GhcApiWrap.hs,实现类型安全的动态调用:
module GhcApiWrap ( msLoadModuleAndExecute ) where import GHC import GHC.Paths (libdir) import Data.Dynamic (fromDynamic) import Unsafe.Coerce (unsafeCoerce) msLoadModuleAndExecute :: String -> String -> String -> Char -> IO (Maybe Int) msLoadModuleAndExecute objDir modulePath funcName arg = runGhc (Just libdir) $ do dflags <- getSessionDynFlags -- 调整动态编译的参数,添加模块搜索路径 let newDflags = dflags { ghcLink = LinkInMemory, ghcMode = CompManager, objectDir = Just objDir, hiDir = Just objDir, importPaths = [objDir] } -- 应用新的动态标志 (_updatedDflags, _) <- setSessionDynFlags newDflags -- 设置编译目标 target <- guessTarget modulePath Nothing setTargets [target] -- 加载并编译目标模块 ret <- load LoadAllTargets case ret of Succeeded -> do -- 解析导入声明并设置上下文 let moduleName = takeWhile (/= '.') modulePath importDecl <- parseImportDecl $ "import " ++ moduleName setContext [IIDecl importDecl] -- 编译带参数的函数调用表达式 dynVal <- dynCompileExpr $ funcName ++ " " ++ show arg -- 安全转换为目标类型 return $ fromDynamic dynVal _ -> return Nothing
3. 调整Main.hs的调用逻辑
import qualified GhcApiWrap as Ghw main :: IO () main = do result <- Ghw.msLoadModuleAndExecute "../dyn/" "DynExample.hs" "nFromChar" 'A' print result
关键说明
libdir是GHC的系统库目录,由ghc-paths提供,必须正确传入runGhc,否则GHC无法找到自身的配置文件(如settings)。importPaths需要包含目标模块所在的目录,确保GHC能找到要编译的.hs文件。- 使用
fromDynamic进行安全类型转换,若类型不匹配会返回Nothing;如果确认类型完全匹配,也可以用unsafeCoerce直接转换函数类型:
注意:-- 直接获取函数并调用的方式 dynFunc <- dynCompileExpr funcName let func = unsafeCoerce dynFunc :: Char -> Int return $ Just (func arg)unsafeCoerce存在运行时风险,仅在确保类型绝对匹配时使用。
内容的提问来源于stack exchange,提问作者Jörg Brüggmann
相关产品推荐
相关产品推荐

