2014-09-08 9 views
7

Buona giornata a tutti.Come reinterpretare un termine DSL nell'approccio finale senza tag?

La nostra applicazione utilizza un DSL tipizzato per descrivere determinate logiche di business. La DSL viene fornita con diversi interpreti senza tag.

Ecco come i suoi termini sono dichiarati:

{-# LANGUAGE TypeFamilies #-} 
{-# LANGUAGE EmptyDataDecls #-} 

class Ctl impl where 
    -- Lift constants. 
    cnst :: Show t => t -> impl t 
    -- Obtain the state. 
    state :: impl (Maybe Int) 

    -- Test for equality. 
    eq :: impl Int -> impl Int -> impl Bool 
    -- If-then-else. 
    ite :: impl Bool -> impl t -> impl t -> impl t 

    -- Processing outcomes. 
    retry :: impl Outcome 
    finish :: impl Outcome 

    -- Require a value. 
    req :: impl (Maybe t) -> impl t 

logica di business viene quindi descritto utilizzando blocchi di codice in questo DSL:

proc1 :: Ctl impl => impl Outcome 
proc1 = ite (req state `eq` cnst 5) finish retry 

Queste definizioni di alto livello sono messi da utilizzare con gli interpreti. Ho un interprete di testo per ottenere una descrizione testuale leggibile di come processi di business sono definiti:

newtype TextE t = TextE { evalText :: String } 

instance Ctl TextE where 
    cnst v = TextE $ show v 
    state = TextE "My current state" 
    eq v1 v2 = TextE $ concat [evalText v1, " equals ", evalText v2] 
    ite cond t e = 
    TextE $ 
    concat ["If ", evalText cond, ", then ", evalText t, ", else ", evalText e] 
    retry = TextE "Retry processing" 
    finish = TextE "Finish" 
    req v = TextE $ concat ["(", evalText v, ")*"] 

Interpretare il DSL con texte produce una stringa:

λ> (evalText proc1) :: String 
"If (My current state)* equals 5, then Finish, else Retry processing" 

Tale descrizione viene utilizzato come riferimento per utenti/analisti.

posso valutare anche un termine DSL al metalinguaggio (Haskell) con altro interprete, che è come l'applicazione segue in realtà le regole:

newtype HaskellE t = HaskellE { evalHaskell :: HaskellType t } 

-- Interface between types of DSL and Haskell. 
type family HaskellType t 

instance Ctl HaskellE where 
    cnst v = HaskellE v 
    state = HaskellE dummyState 
    eq v1 v2 = HaskellE $ evalHaskell v1 == evalHaskell v2 
    ite cond t e = 
    HaskellE $ 
    if (evalHaskell cond) 
    then (evalHaskell t) 
    else (evalHaskell e) 
    retry = HaskellE $ print "Retrying..." 
    finish = HaskellE $ print "Done!" 
    req [email protected](HaskellE v) = 
    case v of 
     Just v' -> HaskellE v' 
     Nothing -> 
     HaskellE (error $ 
        "Could not obtain required value from ") -- ++ evalText term) 

-- Dummy implementations so that this post may be evaluated 
dummyState = Just 5 
type Outcome = IO() 
type instance HaskellType t = t 

Questo interprete produce codice Haskell eseguibile:

λ> (evalHaskell proc1) :: IO() 
"Done!" 

Ora per il mio problema: mi piacerebbe utilizzare l'interprete TextE dall'interprete HaskellE . Ad esempio, desidero definire il ramo guasto di req in un modo che include la rappresentazione testuale del termine nidificato (normalmente ottenuto da evalText term) nel messaggio di errore. Il codice rilevante è commentato nell'implementazione req per HaskellE precedente. Se il commento viene ripristinato, il codice è simile

HaskellE (error $ 
       "Could not obtain required value from " ++ evalText term) 

Tuttavia, il sistema di tipo mi impedisce di fare questo:

tagless.lhs:90:71: Couldn't match expected type ‘TextE t0’ … 
       with actual type ‘HaskellE (Maybe t)’ 
    Relevant bindings include 
     v :: HaskellType (Maybe t) 
     (bound at /home/dzhus/projects/hs-archive/tagless.lhs:85:22) 
     term :: HaskellE (Maybe t) 
     (bound at /home/dzhus/projects/hs-archive/tagless.lhs:85:7) 
     req :: HaskellE (Maybe t) -> HaskellE t 
     (bound at /home/dzhus/projects/hs-archive/tagless.lhs:85:3) 
    In the first argument of ‘evalText’, namely ‘term’ 
    In the second argument of ‘(++)’, namely ‘evalText term’ 
Compilation failed. 

Il messaggio in pratica dice che l'interprete HaskellE è già stato scelto quando il il tipo variabile impl è stato istanziato e io non è possibile utilizzare l'interprete TextE dall'interno di HaskellE.

Quello che non riesco a capire è come reinterpretare un termine da HaskellE a TextE?

Se io sono del tutto sbagliato qui, come faccio a rimodellare il mio approccio in modo che possa effettivamente utilizzare l'interprete testo dal Haskell uno senza ri-attuazione all'interno HaskellE? Sembra che sia abbastanza fattibile con approccio iniziale anziché finale.

Ho rimosso la mia DSL effettiva e semplificato i tipi e gli interpreti per motivi di concisione.

+1

(ciò che è " tagless "nel contesto?) – user2864740

+2

@ user2864740 Questo documento spiega cosa significa; discute quali tag sono nella sezione 3.1: http://okmij.org/ftp/tagless-final/course/lecture.pdf –

+0

@DavidYoung Grazie! – user2864740

risposta

7

È possibile tenere traccia sia del valore sia delle informazioni sull'espressione che ha creato il valore. Se lo fai, perderai alcuni dei vantaggi prestazionali della tua ultima rappresentazione senza tag.

data Traced t a = Traced {evalTraced :: HaskellType a, trace :: t a} 

ci aspettiamo di usarlo con una traccia TextE, quindi dovremo definire quanto segue per convenienza

evalTextTraced :: Traced TextE a -> HaskellType a 
evalTextTraced = evalTraced 

Questa classe ci permette di recuperare i messaggi di errore da un trace

class Show1 f where 
    show1 :: f a -> String 

instance Show1 TextE where 
    show1 = evalText 

instance (Show1 t) => Show1 (Traced t) where 
    show1 = show1 . trace 

Questo interprete conserva una traccia di qualsiasi altro interprete Ctl t da cui possiamo recuperare i messaggi di errore durante l'interpretazione di Traced t.

instance (Show1 t, Ctl t) => Ctl (Traced t) where 
    cnst v = Traced v (cnst v) 
    state = Traced dummyState state 
    eq (Traced v1 t1) (Traced v2 t2) = Traced (v1 == v2) (eq t1 t2) 
    ite (Traced vc tc) (Traced vt tt) (Traced ve te) = Traced (if vc then vt else ve) (ite tc tt te) 
    retry = Traced (print "Retrying...") retry 
    finish = Traced (print "Done!") finish 
    req (Traced v t) = 
     case v of 
      Just v' -> Traced v' rt 
      Nothing -> Traced (error ("Could not obtain required value from " ++ show1 rt)) rt 
     where rt = req t 

Il vostro esempio si comporta come previsto

print . evalText . trace $ proc1 
evalTextTraced proc1 

"If (My current state)* equals 5, then Finish, else Retry processing" 
"Done!" 

Possiamo ancora evalText un esempio con un requisito fallito, ma il tentativo di eseguire produce un messaggio di errore informativo

proc2 :: Ctl impl => impl Outcome 
proc2 = ite (req (cnst Nothing) `eq` cnst 5) finish retry 

print . evalText . trace $ proc2 
evalTextTraced proc2 

"If (Nothing)* equals 5, then Finish, else Retry processing" 
finaltagless.hs: Could not obtain required value from (Nothing)*