2016-04-03 6 views
1

Dies ist Follow-up zu meinem vorherigen question über Typ-indexierte Karte. Nach der Diskussion in den Kommentaren poste ich hier das eigentliche Problem, um zu sehen, ob es eine saubere Möglichkeit gibt, dies zu lösen, indem man eine abhängige Programmierung verwendet.Weitergabe von Laufzeitinformationen für polymorphe Funktion

Das Problem ist, dass bestimmte Laufzeit Informationen, die wir für eine Typklassenfunktion benötigen - wir haben verschiedene Arten von Nachrichten über die Leitung kommen, und wir verwenden diese Funktion zu tun Nachricht spezifische Verarbeitung mit der Laufzeitkonfiguration - wie Übergeben wir Laufzeitinformationen (eine Laufzeitkonfiguration für jede Typinstanz) an diese Funktion?

Toy Code unten mit Kommentaren - wir typeclass Funktion f innerhalb g verwenden, die Laufzeitinformationen erhält und wendet sie auf f - btw, der Satz von Nachrichtentypen a ist fest - so können wir geschlossen typefamilies verwenden, wenn es sein muss:

module Main where 

main :: IO() 
main = do 
    let runtimeinfo = Mesg { aString = "someheader"} 
     builder = (\x -> Mesg { aString = (aString runtimeinfo) ++ (aString x)}) 
    -- should call function "app" on this line which will call function "g" 
    return() 

data Mesg = Mesg {aString :: String} deriving Show 

class Process a where 
    f :: a -> a -- FYI, in actual app, output type is (StateT a IO Builder) 
    -- We can't define builder below at compile-time because it is created at run-time in main above 
--builder :: a -> a 

instance Process Mesg where 
    f inp = Mesg { aString = (reverse (aString inp))} -- contrived example - a placeholder for some processing on message 

-- g is not directly reachable from main - main calls a function "app" which 
-- calls g (after receiving "inp" of type "a" over the wire) - so, to pass 
-- builder, we have to pass it along. builder is from runtime configuration - 
-- in this example, it is created in main. If it were part of typeclass Process, 
-- we won't need to pass it along 
g :: Process a => (a -> a) -> a -> a 
g builder inp = builder $ f inp -- we call processing function f here with runtime builder 

-- Alternative approach pseudo code - map here is created in main, and passed to g via app 
{-- 
g :: (Process a, Typeable a) => Map String Dynamic -> a -> Maybe a 
g map inp = (retrieve corresponding builder from map using type-indexed string), apply here with f 
--} 

Meine Lösung so weit ist typ indiziert Karte in g die builder für den Typ a nachschlägt.

+0

Ich denke, die Art von 'g' ist seltsam .. Warum gibt es eine Zeichenfolge zurück? Warum nicht mit einer Funktion ':: (Prozess a, Typable a) => Map String Dynamisch -> Maybe (a -> a)' beginnen, die den String aus dem 'TypeRep' berechnet und die gewünschte Funktion zurückgibt (in Maybe von Kurs). – user2407038

+0

@ user2407038, meine schlechte über kommentierte Version von 'g'. Sollte zurückgeben "Vielleicht ein". Fest. – Sal

Antwort

3

Diese Frage ist wirklich nicht in sich abgeschlossen, aber es klingt, als könnten Sie die Einrichtungen im reflection Paket verwenden. Insbesondere können Sie Laufzeitinformationen in Typklasseninstanzen verwenden. Zum Beispiel könnten Sie so etwas schreiben (ungetestet).

{-# LANGUAGE ScopedTypeVariables, 
     UndecidableInstances, .... #-} 
import Data.Proxy 
import Data.Reflection 

data Configuration a = Configuration 
    { the_builder :: a -> a 
    , someOtherThing :: Int 
    , whatever :: Char } 

class Buildable a where 
    builder :: a -> a 

newtype Yeah s a = Yeah a 

instance Reifies s (Configuration a) => 
      Buildable (Yeah s a) where 
    builder (Yeah x) = Yeah $ the_builder (reflect (Proxy :: Proxy s)) x 

Dann können Sie schreiben

reify config $ \(_ :: Proxy s) -> expr 

und innerhalb expr, die Art Yeah s a wird eine Instanz der Buildable Klasse sein.

+0

Wenn wir remify innerhalb von main aufrufen, ist die erzeugte Instanz außerhalb des Hauptkörpers sichtbar, d. H. Sie hat einen globalen Gültigkeitsbereich wie die typeclass-Instanz zur Kompilierungszeit? Dies ist eine Sache bei der Reflexion dynamischer Typklasseninstanzen, die mir nicht klar war. – Sal

+4

@Sal Die Instanz 'Buildable (Yeah s a)' ist eine normale Instanz; Es hat einen globalen Gültigkeitsbereich wie jede Instanz. Sie können es nur ohne den 'Conferences s (Configuration a)' -Kontext nicht erfüllen, was bedeutet, dass die Instanz nur _usable_ in einem Aufruf von 'retify' ist. In der Praxis bedeutet dies, dass Sie keine zweite Instanz "Buildable (Yeah s a)" schreiben können, die diese überlappt, da GHC niemals Instanzkontexte verwendet, um zwischen zwei Instanzen zu wählen. –

1

Wenn Sie in der Lage sind, die Deklaration builder zu ändern, warum nicht es die altmodische Art und Weise tun und nur in der Konfiguration mit einer Monade?

data Config = Config { 
    theHeader :: String, 
    somethingElse :: Int, 
    andAnotherThing :: Bool 
} 

class Buildable a where 
    build :: MonadReader Config m => String -> m a 


data Msg = Msg { msg :: String } deriving (Show) 

instance Buildable Msg where 
    build body = do 
     config <- ask 
     return $ Msg { msg = theHeader config ++ body } 


-- imagine we're getting this from a file in the IO monad 
readConfigFile = return $ Config { 
    theHeader = "foo", 
    somethingElse = 4, 
    andAnotherThing = False 
} 
-- imagine we're reading this from the wire 
getFromSocket = return "bar" 

main = do 
    config <- readConfigFile 
    body <- getFromSocket 
    msg <- runReaderT (build body) config 
    print msg 

@reflection Antwort des dfeuer wird nützlich, wenn Sie die Klasse nicht besitzen und die monadischen Kontext build ‚s Typ nicht hinzufügen können.

+0

Yep, ich besitze nicht die 'Build' – Sal

+0

Sie besitzen nicht die' Process' Klasse, die Sie in Ihrer Frage hatten? –

+0

Ja, ich besitze die Klasse 'Process'. – Sal