Gestione di continuazioni all'interno di una coda di priorità

3

Sto tentando di determinare il modo migliore per gestire le azioni che devono verificarsi nei passaggi. Molte di queste azioni utilizzano oggetti che sono stati creati in un passaggio precedente. La soluzione che ho trovato è quella di implementare un builder di calcolo.

let actions =
    passHandler {
        let myObject = pass1Action()

        do! waitForPass2

        pass2Action myObject
    }

In questo codice waitForPass2 farebbe sì che la continuazione venisse inserita in una coda di priorità che causerà l'esecuzione nel secondo passaggio. Tuttavia, questa soluzione mi sembra un po 'come un trucco. Vorrei utilizzare un approccio più funzionale, ma la libreria che sto usando non è funzionale e probabilmente sarebbe troppo impegnativo da completare. C'è una soluzione migliore a questo?

Nota: ho tentato di mantenere la domanda generica, ma qui ci sono alcune specifiche nel caso in cui aiuta. Questo è per la fase di emissione di un compilatore scritto usando Mono.Cecil. Sono necessarie fasi diverse per separare la creazione e l'utilizzo degli oggetti TypeDefinition e MethodDefinition.

    
posta MI3Guy 10.02.2016 - 02:20
fonte

1 risposta

1

Questa risposta è più o meno nel contesto del tuo attuale approccio piuttosto che in un approccio alternativo completamente diverso.

In primo luogo, la tua soluzione presenta alcuni problemi come sembra. La soluzione avrebbe più senso se il passaggio 1 fosse concomitante con il passaggio 2, piuttosto che una serie sequenziale di passaggi. Invece, considera cosa succederebbe se eseguissi actions due volte in sequenza, cioè li combinassi in sequenza in un'espressione di calcolo più grande: primo pass1Action verrebbe eseguito, quindi smetterebbe di attendere il passaggio 2. Quando passava 2, pass2Action sarebbe Esegui, quindi la seconda istanza di pass1Action verrebbe eseguita, quindi smetterebbe di attendere il passaggio 2 di nuovo (che presumibilmente non sarà mai (?)).

Essenzialmente, il problema è che stai trattando le azioni del passaggio 2 come sincronizzate in modo sincrono dal passaggio 1 che non ha senso: passare 2 azioni non possono restituire i valori per passare 1. Chiamare un passaggio successivo da una passata precedente dovrebbe essere visualizzato come una chiamata asincrona non di ritorno.

Puoi correggere questo e semplificare la tua implementazione (non avendo bisogno di usare le continuazioni) usando monitore Writer o Output . Fondamentalmente, si aggiunge un'operazione runInNextPass (o anche runInPass che prende quale pass a target come parametro). Il tuo codice sarà quindi il seguente:

let actions =
    passHandler {
        let myObject = pass1Action()
        runInNextPass pass2Action
    }

runInNextPass è quindi solo tell o una leggera variante dell'articolo. L'esecuzione del calcolo nel suo complesso lo farebbe funzionare normalmente, ottenendo un valore di Writer , quindi eseguendo il valore di output. Funziona bene se vuoi eseguire solo N passaggi per un numero di passate noto (ma probabilmente solo al runtime): basta ripetere il pattern di esecuzione N volte. (Nella sua forma N = 2, questo è un modello a portata di mano quando si vuole eseguire un codice che richiede una fase di inizializzazione prima di una "vera" fase di esecuzione.) Tuttavia, semplicemente cambiando il monoide che stiamo usando (vedere la articolo), possiamo ottenere un paio di effetti diversi.

Lo scenario sopra riportato corrisponde ad avere l'output del Writer monad, cioè il monoide, che è o le azioni che hanno effetti collaterali (o una monade IO) rappresentate come funzioni di tipo () -> () o Writer monade stessa . Ogni strato di costruttori di tipo Writer corrisponde a un passaggio aggiuntivo. Quindi Writer<() -> (), 'a> rappresenta un sistema a 2 passaggi, Writer<Writer<() -> (), ()>,a> rappresenta un sistema a 3 passaggi e così via. In questo caso, il numero di passaggi verrà applicato in modo statico, non è possibile utilizzare un'operazione di passaggio 2 (digitare Writer<() -> (), ()> ) nel passaggio 3 (operazioni di tipo () -> () ). Se si desidera un numero di passaggi non noto staticamente, si crea il tipo ricorsivo type Pass<'a> = NextPass of Writer<Pass (), 'a> | FinalPass of () -> 'a . Se F # supporta il polimorfismo di tipo superiore, potresti scrivere qualcosa come

type GPass<'f, 'a> = NextPass of Writer<'f<GPass<'f, 'a>>
                   | FinalPass of () -> 'a

Il vantaggio di questo è che possiamo cambiare quale monoid stiamo usando. Ad esempio, quando 'f è Option , allora possiamo avere una sequenza di passaggi che vengono eseguiti fino a quando non vengono richiesti passaggi successivi. Quando 'f è una variante di Map puoi avere passaggi che sono etichettati in modo tale che il tuo pass "parse" potrebbe richiedere direttamente il lavoro da svolgere nel passaggio "ottimizzazione" senza dover sapere quanti passaggi sono intermedi. In questo approccio, tuttavia, potresti voler passare informazioni su quali passaggi sono già stati eseguiti (possibilmente aggiungendo un aspetto di Reader o Environment monad), perché non c'è nulla che impedisca il passaggio di "ottimizzazione" dalla richiesta di lavoro da eseguire nel " parse "pass.

    
risposta data 10.02.2016 - 05:25
fonte

Leggi altre domande sui tag