2015-05-09 54 views
6

Sto lavorando ad un progetto haskell in cui le impostazioni si trovano attualmente in un file chiamato Setting.hs, quindi vengono verificate durante la compilazione e sono accessibili a livello globale.Passaggio dalla configurazione statica alla configurazione dinamica

Tuttavia, poiché questo è un po 'troppo statico, stavo considerando di leggere la configurazione durante il runtime. Il codebase è enorme e sembra che sarebbe un notevole sforzo passare l'impostazione, ad es. come argomento attraverso l'intero flusso del programma, dal momento che possono essere arbitrariamente accessibili da qualsiasi luogo.

Esistono modelli di progettazione, librerie o anche estensioni ghc che possono aiutare qui senza dover refactoring dell'intero codice?

+0

Gli argomenti impliciti o la monade del lettore sono scelte comuni, ma richiedono alcuni cambiamenti. – chi

+1

Dai un'occhiata a [Configurazioni implicite - o, le classi di tipi riflettono i valori dei tipi] (http://okmij.org/ftp/Haskell/types.html#Prepose), se potesse essere d'aiuto. –

+2

Espansione sul commento di @Petr Pudlák, è possibile trovare un'implementazione di configurazioni implicite nel pacchetto 'reflection'. Nella cartella degli esempi dal repository c'è un esempio di tipo "lettore" che sembra pertinente: https://github.com/ekmett/reflection/blob/master/examples/ReaderLike.hs. Vedi anche questa risposta SO per un esempio di utilizzo: http://stackoverflow.com/a/29929718/1364288 – danidiaz

risposta

0

Quello che stai chiedendo, se fosse possibile, spezzerebbe la trasparenza referenziale, almeno per la pura funzione (un risultato di pura funzione può dipendere da alcune variabili globali ma non da un file di configurazione non è possibile)?

Di solito le persone evitano questo tipo di situazione passando implicitamente la configurazione come dati tramite una Monade. In alternativa (se sei felice di refactoring il tuo codice un po ') è possibile utilizzare il implicit parameter extenson, che in teoria è stato fatto per risolvere quel tipo di problema, ma in pratica non funziona davvero. Tuttavia, se proprio ne hai bisogno, puoi usare unsafePerformIO e ioRef per avere un top level mutable state che è sporco e accigliato upton. È necessario uno stato mutabile di livello superiore, perché è necessario essere in grado di modificare "mutare" la configurazione iniziale quando la si carica.

Quindi si ottiene cose del genere:

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

Grazie per i suggerimenti! Mi si avvicinò con un esempio minimo che mostra come andrò su di esso con il pacchetto reflection:

{-# 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 = (+) 

La classe Given è un po 'più facile lavorare con e perfettamente adatto per un modello di configurazione globale. Tutte le funzioni che non utilizzano given (che ottiene il valore) non sembrano aver bisogno del vincolo di classe. Ciò significa che devo solo cambiare le funzioni che effettivamente accedono alla configurazione globale.

Questo è quello che stavo cercando.

+0

In che modo è diverso dai parametri impliciti. Se 'f' chiama' g' che ha bisogno del vincolo, anche 'f' ha bisogno del vincolo? Altrimenti, 'g' ottiene il valore? – mb14

+0

Sì, anche 'f' ha bisogno del vincolo. Ho aggiornato il codice. Inoltre, la tua domanda è eccellente e posso solo vagamente rispondere. Ma il documento su cui si basa la riflessione su http://okmij.org/ftp/Haskell/tr-15-04.pdf menziona diversi vantaggi rispetto ai "parametri impliciti" nell'Introduzione e nella Sezione 6.2. Si suppone che la riflessione giochi più "bene" con il sistema di tipi e supporti meglio set di parametri simultanei. – hasufell

+0

'give' è considerato malvagio. Edward Kmett ha indicato che se altre persone non avessero obiettato così fortemente, l'avrebbe rimosso del tutto. – dfeuer

Problemi correlati