2012-02-01 23 views
6

Sto emulando un microprocessore a 4 bit. Ho bisogno di tenere traccia dei registri, della memoria e dell'output in esecuzione (punti bonus per avere anche un contatore di cicli fetch-execute). Sono riuscito a farlo senza monade, ma mi sembra complicato passare così tante cose contemporaneamente in modo esplicito. Anche la definizione della funzione è disordinata, lunga e difficile da leggere.Diversi livelli di stato interagenti in haskell

Ho provato a farlo con le Monade e semplicemente non combacia. Ho provato a trattare tutti i componenti di stato separati come un singolo tipo, ma questo mi ha lasciato il problema di cosa fare il valore.

State Program() -- Represents the state of the processor after a single iteration of the fetch execute cycle 

Era l'unico tipo che aveva senso. Ma a quel punto perché preoccuparsi? Ho provato diffondendola tirando la stringa dal mio tipo composito e trattandolo come valore

State Program' String 

che funzionava bene, tranne per il fatto che mi serviva uscita RUNNING. Non importa quello che ho fatto, non potevo aggrapparmi alla stringa e allo stato contemporaneamente.

Ora sto provando a cimentarmi con i trasformatori monad. Sembra che devo separare tutti i diversi livelli di stato. Ma la mia testa sta esplodendo velocemente.

StateT Registers (StateT Memory (State Output)) a = 
StateT (registers -> (StateT Memory (State Output)) (a,registers)) 

StateT Registers (StateT Memory (State Output)) a = 
StateT (registers -> (Memory -> (Output -> (((a,Registers),Memory),Output)))) 

Non ho ancora inserito il contatore di FEcycle!

Domande:

  1. Sono sulla strada giusta?
  2. Visto che sto estraendo i trasformatori monad ora, è possibile smettere di trattare "l'uscita in esecuzione" come stato e trasferirlo alla monade IO? Sarebbe fantastico, invece di aggrapparmici potrei semplicemente stamparlo.
  3. Quanti layer dovrei separare in stato? Riesco a vedere due strati distinti, ma dipendono l'uno dall'altro da vicino (sia la memoria che i registri dipendono dallo stato sia della memoria che dei registri). Dovrei tenerli insieme come un singolo stato o separarli e impilarli? Quale approccio produrrà il codice più leggibile?
+1

Forse "l'uscita in esecuzione" potrebbe essere rappresentata al meglio utilizzando la monade Writer (consultare http://monads.haskell.cz/html/writermonad.html)? –

risposta

9

Stratificare più monadi di stato l'uno sopra l'altro è una cattiva idea: dovrete comporre un gruppo di lift s per ottenere in ogni pezzo di stato, identificato solo dal numero di livelli in basso nello stack che è. Che schifo! In effetti, la libreria mtl in generale è progettata per essere utilizzata, con rare eccezioni, con un trasformatore monad di ciascun "tipo" in una pila.

Invece, vorrei suggerire StateT Program IO(). L'interfaccia con lo stato è la stessa e puoi, come hai detto, produrre in IO semplicemente usando liftIO. Certo, il tipo di valore è (), ma cosa c'è di sbagliato in questo? Non esiste un valore rilevante per il quale il valore può restituire dall'emulatore di livello superiore. E, naturalmente, è probabile che tu abbia componenti più piccoli e riutilizzabili come parte del tuo emulatore e quelli con i relativi tipi di risultati. (Infatti, get è uno di questi componenti.) Non c'è niente di sbagliato nell'assenza di un valore di ritorno significativo al livello più alto.

Per quanto riguarda l'accesso comodo a ciascuna parte dello stato, quello che stai cercando sono le lenti; this Stack Overflow answer è un'ottima introduzione. Ti permettono di accedere in modo semplice e facile e modificare parti indipendenti del tuo stato. Ad esempio, con l'implementazione data-lens, è possibile scrivere facilmente qualcosa come regA += 1 per incrementare regA o stack %= drop 2 per rimuovere i primi due elementi dello stack.

Certo, è essenzialmente trasformando il codice in mutazione imperativo di un insieme di variabili globali, ma questo è in realtà un vantaggio, dato che è esattamente il paradigma della CPU si sta emulando ha sede a. E con il pacchetto data-lens-template , è possibile ricavare questi obiettivi da una definizione di record in una singola riga.

+0

È dannatamente fantastico. Tempo di imparare template haskell. È ancora facile trovare il supporto di definizioni funzionali come regA + = 1? Perché mentre ciò è bello e leggibile e esprime chiaramente l'intento (molto imperativo), non assomiglia a un codice funzionale. – TheIronKnuckle

+1

Non è necessario imparare Template Haskell per utilizzare il modello data-lens; basta inserire '{- # LANGUAGE TemplateHaskell # -}' nella parte superiore del file e 'makeLenses ['' Program]' da qualche parte sotto la definizione di 'Program'. Per quanto riguarda la definizione di '(+ =)', sicuro; sono solo semplici wrapper dell'API core lens per 'StateT'; [la fonte] (http://hackage.haskell.org/packages/archive/data-lens/2.0.2/doc/html/src/Data-Lens-Strict.html) è collegata dalla documentazione di Hackage. – ehird

+2

Minori cavilli: le monadi di stato a strati sono una cattiva idea - a meno che tu non le voglia davvero. Consentono il partizionamento dello stato, quindi è possibile limitare l'accesso in lettura a un determinato livello dello stato piuttosto che l'accesso in lettura e scrittura, questo viene utilizzato per il codice "attenti alla sicurezza". Altrimenti buona risposta. –

2

Un modo semplice per farlo sarebbe quello di creare un tipo di dati che rappresenta i registri e memoria:

data Register = ... 
data Memory = ... 
data Machine = Machine [Register] Memory 

Poi hanno alcune funzioni che aggiornano il registri/memoria. Ora utilizzare questo tipo per il tuo stato e l'uscita per il tipo:

type Simulation = State Machine Output 

Ora ogni operazione potrebbe essere nella forma:

operation previous = do machine <- get 
         (result, newMachine) <- operate on machine 
         put newMachine 
         return result 

Qui previous è l'uscita precedente della macchina. Puoi incorporarlo anche nel risultato.

Quindi il tipo Machine rappresenta lo stato della macchina; stai trasmettendo l'output delle operazioni precedenti.

Un modo più sofisticato sarebbe utilizzare state threads (Control.Monad.ST). Questi ti consentono di utilizzare riferimenti e array mutabili all'interno di una funzione pur garantendo la purezza all'esterno.

Problemi correlati