Haskell,GHC 8:动态加载/导入模块

我需要有类似的东西

-- Main.hs
module Main where

main :: IO ()
main = do
  <import Plugin>
  print Plugin.computation

带有一个插件

-- Plugin.hs
module Plugin where

computation :: Int
computation = 4

但是,我需要将该插件与主应用程序一起编译。 他们需要一起部署。 只有模块的导入(不是编译)应该动态发生。

我发现动态加载编译好的Haskell模块--HGHC 7.6,GHC 8.0.2工作得很好,除了在执行应用程序时需要插件的源文件在当前工作目录中。


编辑(07.12.2017)

是否可以使用GHC API从字符串加载模块而不是文件? http://hackage.haskell.org/package/ghc-8.2.1/docs/GHC.html#t:Target表明它是可能的,但文档有很多漏洞,我找不到实际执行此操作的方法。 如果这可以完成,我可以使用文件嵌入来将插件源文件包含到编译后的二进制文件中。 例:

module Main where

-- Dynamic loading of modules
import GHC
import GHC.Paths ( libdir )
import DynFlags
import Unsafe.Coerce

import Data.Time.Clock (getCurrentTime)
import StringBuffer

pluginModuleNameStr :: String
pluginModuleNameStr = "MyPlugin"

pluginSourceStr :: String
pluginSourceStr = unlines
  [ "module MyPlugin where"
  , "computation :: Int"
  , "computation = 4"
  ]

pluginModuleName :: ModuleName
pluginModuleName = mkModuleName pluginModuleNameStr

pluginSource :: StringBuffer
pluginSource = stringToStringBuffer pluginSourceStr

main :: IO ()
main = do
    currentTime <- getCurrentTime
    defaultErrorHandler defaultFatalMessager defaultFlushOut $ do
      result <- runGhc (Just libdir) $ do
        dflags <- getSessionDynFlags
        setSessionDynFlags dflags
        let target = Target { targetId = TargetModule $ pluginModuleName
                            , targetAllowObjCode = True
                            , targetContents = Just ( pluginSource
                                                    , currentTime
                                                    )
                            }
        setTargets [target]
        r <- load LoadAllTargets
        case r of
          Failed    -> error "Compilation failed"
          Succeeded -> do
            setContext [IIDecl $ simpleImportDecl pluginModuleName]
            result <- compileExpr ("MyPlugin.computation")
            let result' = unsafeCoerce result :: Int
            return result'
      print result

但是,这会导致

<command-line>: panic! (the 'impossible' happened)
  (GHC version 8.0.2 for x86_64-apple-darwin):
    module ‘MyPlugin’ is a package module

编辑(08.12.2017)

我可以直接将“插件”编译成最终的二进制文件,方法是将源文件写入临时文件,然后像链接文章(动态加载编译的Haskell模块-GHC 7.6)一样加载它。 但是,如果插件从Hackage导入软件包,这并不会很好:

module Main where

import Control.Monad.IO.Class (liftIO)
import DynFlags
import GHC
import GHC.Paths (libdir)
import System.Directory (getTemporaryDirectory, removePathForcibly)
import Unsafe.Coerce (unsafeCoerce)

pluginModuleNameStr :: String
pluginModuleNameStr = "MyPlugin"

pluginSourceStr :: String
pluginSourceStr = unlines
  [ "module MyPlugin where"
  , "import Data.Aeson"
  , "computation :: Int"
  , "computation = 4"
  ]

writeTempFile :: IO FilePath
writeTempFile = do
  dir <- getTemporaryDirectory
  let file = dir ++ "/" ++ pluginModuleNameStr ++ ".hs"
  writeFile file pluginSourceStr
  return file

main :: IO ()
main = do
  moduleFile <- writeTempFile
  defaultErrorHandler defaultFatalMessager defaultFlushOut $ do
    result <- runGhc (Just libdir) $ do
      dflags <- getSessionDynFlags
      setSessionDynFlags dflags
      target <- guessTarget moduleFile Nothing
      setTargets [target]
      r <- load LoadAllTargets
      liftIO $ removePathForcibly moduleFile
      case r of
        Failed -> error "Compilation failed"
        Succeeded -> do
          setContext [IIDecl $ simpleImportDecl $ mkModuleName pluginModuleNameStr]
          result <- compileExpr "MyPlugin.computation"
          let result' = unsafeCoerce result :: Int
          return result'
    print result

例如,当MyPlugin包含语句import Data.Aeson时,是否有加载包的方法? 如果我将它添加到插件字符串,它将失败

/var/folders/t2/hp9y8x6s6rs7zg21hdzvhbf40000gn/T/MyPlugin.hs:2:1: error:
    Failed to load interface for ‘Data.Aeson’
    Perhaps you meant Data.Version (from base-4.9.1.0)
    Use -v to see a list of the files searched for.
haskell-loader-exe: panic! (the 'impossible' happened)
  (GHC version 8.0.2 for x86_64-apple-darwin):
  Compilation failed
CallStack (from HasCallStack):
  error, called at app/Main.hs:40:19 in main:Main

我的请求的原因是数据库支持:我们使用Persistent来访问数据库,并且需要动态导入来支持多个数据库(MySQL,PostgreSQL和SQLite),同时仍允许最终用户只安装三个数据库服务器之一换句话说:如果用户只使用PostgreSQL,则不要求用户安装它们)。 只有在用户实际配置主应用程序以使用该模块时,才能加载特定于数据库的模块。

如果我不import Database.Persist.MySQL ,那么应用程序不需要安装MySQL。 否则,应用程序将失败,例如,

dyld: Library not loaded: 
/usr/local/opt/mysql/lib/libmysqlclient.20.dylib

在macOS上。


具有匹配模块名称的文件必须以其外观存在 - 文件的内容似乎并不重要。

在Linux上,我甚至可以将它作为/ dev / null的符号链接,并且事情可以工作,但是它本身的符号链接不会。

链接地址: http://www.djcxy.com/p/7527.html

上一篇: Haskell, GHC 8: dynamically load/import module

下一篇: get the renamed (with fully qualified import) haskell AST using ghc api