2017-12-06 4 views
10

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.

+0

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 –

+0

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

+0

Ich habe den Post bearbeitet, um meine derzeitige Vorgehensweise zu zeigen. – eugenk

Antwort

1

Eine Datei mit einem passenden Modulnamen muss nach dem Aussehen existieren - es scheint nicht wichtig zu sein, was der Inhalt der Datei ist.

Unter Linux kann ich es sogar zu einem Symlink zu/dev/null machen und die Dinge funktionieren, aber ein Symlink zu sich selbst nicht.

+0

Wenn jemand weiß, wie man ein Modul erzwingen kann, dann ist vielleicht keine Datei auf der Festplatte erforderlich. Wenn ja, bitte fügen Sie Ihr Wissen hinzu! – codeshot

+0

Auch wenn ein Modul gezwungen wird zu interpretieren, ist eine Datei auf der Festplatte erforderlich. Ich suchte nach Möglichkeiten, die GHC-API, wo sie auf das Dateisystem zugreift, zu haken, aber es sieht so aus, als ob GHC die IO-Monade dafür verwendet und es gibt keine Klasse von ModulStore-Typen, die man implementieren könnte. – codeshot

Verwandte Themen