2015-05-09 13 views
6

Ich arbeite an einem Haskell-Projekt, in dem sich die Einstellungen derzeit in einer Datei mit der Bezeichnung Setting.hs befinden. Daher werden sie während der Kompilierungszeit überprüft und können global abgerufen werden.Von der statischen Konfiguration zur dynamischen Konfiguration wechseln

Da dies jedoch ein bisschen zu statisch ist, überlegte ich, die Konfiguration während der Laufzeit zu lesen. Die Codebasis ist riesig und es scheint, es wäre ein beträchtlicher Aufwand, die Einstellung z.B. als Argument durch den gesamten Programmablauf, da auf sie von irgendwoher beliebig zugegriffen werden kann.

Gibt es Entwurfsmuster, Bibliotheken oder sogar Ghc-Erweiterungen, die hier helfen können, ohne den gesamten Code zu refaktorieren?

+0

Implizite Argumente oder Reader-Monade sind allgemeine Wahl, aber sie erfordern einige Änderungen. – chi

+1

Werfen Sie einen Blick auf [Implizite Konfigurationen - oder, geben Sie Klassen die Werte von Typen wider (http://okmij.org/ftp/Haskell/types.html#Prepose), wenn es helfen könnte. –

+2

Erweiterung auf @Petr Pudlák Kommentar, eine Implementierung von impliziten Konfigurationen finden Sie im Paket "Reflection". Im Beispielordner aus dem Repo gibt es ein "leserähnliches" Beispiel, das relevant erscheint: https://github.com/ekmett/reflection/blob/master/examples/ReaderLike.hs. Siehe auch diese SO-Antwort für ein Anwendungsbeispiel: http://stackoverflow.com/a/29929718/1364288 – danidiaz

Antwort

0

Was Sie fragen, wenn es möglich wäre, würde referentielle Transparenz zumindest für pure Funktion brechen (ein reines Funktionsergebnis kann von einigen globalen Variablen abhängen, aber nicht von einer Konfigurationsdatei, oder?)

Normalerweise vermeiden Menschen diese Art von Situation, indem sie implizit die Konfiguration als Daten über eine Monad übergeben. Alternativ (wenn Sie Ihren Code ein wenig umgestalten möchten) können Sie die implicit parameter extenson verwenden, die in der Theorie zur Lösung dieser Art von Problem gemacht wurde, aber in der Praxis nicht wirklich funktioniert. Jedoch, wenn Sie wirklich brauchen, können Sie unsafePerformIO und ioRef verwenden, um eine top level mutable state zu haben, die schmutziges und verkratztes upton ist. Sie benötigen einen veränderbaren Zustand auf oberster Ebene, da Sie Ihre ursprüngliche Konfiguration ändern können, wenn Sie sie laden.

Dann erhalten Sie die Dinge so:

myGlobalVar :: IORef Int 
{-# NOINLINE myGlobalVar #-} 
myGlobalVar = unsafePerformIO (newIORef 17) 
4

Danke für die Hinweise! Ich kam mit einem minimalen Beispiel auf der zeigt, wie ich es mit dem reflection Paket gehen:

{-# LANGUAGE Rank2Types, FlexibleContexts, UndecidableInstances #-} 

import Data.Reflection 

data GlobalConfig = MkGlobalConfig { 
    getVal1 :: Int 
    , getVal2 :: Double 
    , getVal3 :: String 
} 

main :: IO() 
main = do 
    let config = MkGlobalConfig 1 2.0 "test" 
    -- initialize the program flow via 'give' 
    print $ give config (doSomething 2) 
    -- this works too, the type is properly inferred 
    print $ give config (3 + 3) 
    -- and this as well 
    print $ give config (addInt 7 3) 

-- We need the Given constraint, because we call 'somethingElse', which finally 
-- calls 'given' to retrieve the configuration. So it has to be propagated up 
-- the program flow. 
doSomething :: (Given GlobalConfig) => Int -> Int 
doSomething = somethingElse "abc" 

-- since we call 'given' inside the function to retrieve the configuration, 
-- we need the Given constraint 
somethingElse :: (Given GlobalConfig) => String -> Int -> Int 
somethingElse str x 
    | str == "something"  = x + getVal1 given 
    | getVal3 given == "test" = 0 + getVal1 given 
    | otherwise    = round (fromIntegral x * getVal2 given) 

-- no need for Given constraint here, since this does not use 'given' 
-- or any other functions that would 
addInt :: Int -> Int -> Int 
addInt = (+) 

Die Given Klassen ein bisschen einfacher zu handhaben ist und perfekt geeignet für ein globales Konfigurationsmodell. Alle Funktionen, die given (die den Wert erhält) nicht verwenden, scheinen die Klasseneinschränkung nicht zu benötigen. Das heißt, ich muss nur Funktionen ändern, die tatsächlich auf die globale Konfiguration zugreifen.

Das ist, was ich gesucht habe.

+0

Wie unterscheidet es sich von impliziten Parametern. Wenn 'f'' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' ' Wenn nicht, erhält 'g' den Wert? – mb14

+0

Ja, 'f' benötigt ebenfalls die Einschränkung. Ich habe den Code aktualisiert. Auch deine Frage ist hervorragend und ich kann sie nur vage beantworten. Aber das Papier, das auf der Grundlage von http://okmij.org/ftp/Haskell/tr-15-04.pdf basiert, erwähnt einige Vorteile gegenüber "impliziten Parametern" in der Einleitung und Abschnitt 6.2. Reflection soll mit dem Typsystem "schöner" spielen und unterstützt gleichzeitig mehr Parameter. – hasufell

+0

'give' gilt als böse. Edward Kmett hat angedeutet, dass, wenn andere Leute nicht so stark einwenden würden, er sie vollständig entfernen würde. – dfeuer