ich so etwas wieHaskell, GHC 8: dynamisch laden/Import-Modul
-- Main.hs
module Main where
main :: IO()
main = do
<import Plugin>
print Plugin.computation
Mit einem Plugin wie
-- Plugin.hs
module Plugin where
computation :: Int
computation = 4
Allerdings haben muß, muß ich das Plugin neben der Hauptanwendung erstellt werden . Sie müssen zusammen eingesetzt werden. Nur der Import (nicht die Kompilierung) des Moduls sollte dynamisch erfolgen.
Ich fand Dynamically loading compiled Haskell module - GHC 7.6 auf dem Weg und es funktioniert gut mit GHC 8.0.2 mit Ausnahme der Tatsache, dass es die Quelldatei des Plugins in das aktuelle Arbeitsverzeichnis benötigt, wenn die Anwendung ausgeführt wird.
bearbeiten (07.12.2017)
Ist es möglich, ein Modul aus einem String statt einer Datei mit dem GHC API zu laden? http://hackage.haskell.org/package/ghc-8.2.1/docs/GHC.html#t:Target schlägt vor, dass es möglich ist, aber die Dokumentation hat viele Löcher und ich kann keinen Weg finden, dies tatsächlich zu tun. Wenn dies erreicht werden kann, kann ich file-embed verwenden, um die Plugin-Quelldatei in die kompilierte Binärdatei aufzunehmen. Beispiel:
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
Dies resultiert jedoch in
<command-line>: panic! (the 'impossible' happened)
(GHC version 8.0.2 for x86_64-apple-darwin):
module ‘MyPlugin’ is a package module
bearbeiten (08.12.2017)
ich durch das Schreiben des "Plugin" direkt in die endgültige binäre kompilieren Die Quelle in eine temporäre Datei und dann wie im verknüpften Post (Dynamically loading compiled Haskell module - GHC 7.6) laden. Jedoch spielt dies nicht gut, wenn die Plug-Importe Pakete von 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
Gibt es eine Möglichkeit Pakete zu laden, wenn zum Beispiel MyPlugin
die Anweisung import Data.Aeson
enthält? Wenn ich es in dem Plugin-String hinzufügen, schlägt es mit
/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
Der Grund für meine Anfrage ist Datenbank-Unterstützung: Wir verwenden Persistent eine Datenbank und der dynamische Import zuzugreifen ist notwendig, um mehrere Datenbanken zu unterstützen (MySQL , PostgreSQL und SQLite), während der Endbenutzer immer nur einen der drei Datenbankserver installieren kann (mit anderen Worten: Der Benutzer muss nicht alle installieren, wenn er nur PostgreSQL verwendet). Das datenbankspezifische Modul sollte nur geladen werden, wenn der Benutzer die Hauptanwendung für die Verwendung dieses Moduls tatsächlich konfiguriert.
Wenn ich nicht import Database.Persist.MySQL
, dann erfordert die Anwendung nicht MySQL installiert werden. Anderenfalls schlägt die Anwendung fehl, beispielsweise mit
auf macOS.
Ich habe das Gefühl, dass dies ein Anwendungsfall für ghc [backpack] sein kann (https://plv.mpi-sws.org/backpack/), kann aber nicht sicher sagen, wie ich es nicht benutzt habe doch ich selbst. Es ist auch nur verfügbar auf ghc-8.2 –
Danke, aber ich bin mir nicht sicher, ob wir das Projekt auf GHC 8.2 zu diesem Zeitpunkt aktualisieren können. Außerdem benutzen wir Stack, der momentan nicht mit dem Rucksack kompatibel ist (obwohl er daran arbeitet). – eugenk
Ich habe den Post bearbeitet, um meine derzeitige Vorgehensweise zu zeigen. – eugenk