Converti automaticamente le mutazioni nel codice imperativo in azioni funzionali?

0

Mi chiedo se qualcuno abbia provato a creare un linguaggio in cui si possano scrivere le mutazioni in uno stile imperativo diretto, e il compilatore trasforma automaticamente le modifiche in azioni puramente in stile Redux. Ad esempio (pseudo-js):

atomic function repaint_car(car) {
    car.color = "Blue"
    car.value += 1000
}

sarebbe convertito in:

{
    type: REPAINT_CAR,
    new_color: "Blue",
    delta_value: 1000
}

Per chiarire, non mi aspetto che questo funzioni per il codice arbitrario , naturalmente. Lo applicheresti a un ambito di codice che muta uno o più plain-old -Data (POD) oggetti. La lingua o lo strumento creerebbe quindi un oggetto "azione" equivalente.

Penso che questo sarebbe di aiuto se si desidera modificare qualcosa profondamente annidato nello stato. Spesso è difficile nella programmazione funzionale costruire il nuovo stato, ma nel codice mutevole basta scrivere state.garage[0].cars.cupholder.color = "blue" (come esempio esagerato).

Puoi anche convertire il codice per utilizzare il schema di comando . Sebbene non sia funzionale, contiene la mutazione di stato e svolge un ruolo simile a Redux:

class RepaintCarCommand extends Command {
    constructor(color, value) {
        this.color = color;
        this.value = value;
    }
    function do(car) {
        this.previous_color = car.color;
        this.previous_value = car.value;
        car.color = this.color;
        car.value += this.delta_value;
    }
    function undo(car) {
        car.color = this.previous_color;
        car.value = this.previous_value;
    }
}

Inoltre renderebbe più semplice la transizione dall'app semplice a quella complessa. Se si inizia con una semplice applicazione sequenziale e, a un certo punto, si decide di aggiungere la funzionalità di annullamento, è necessario riscrivere tutto per utilizzare lo schema di comando. Questa funzionalità ti consentirebbe di isolare le mutazioni, senza ristrutturare troppo l'applicazione.

Questa idea ha un nome? Qualche lingua lo implementa? O ci sono dei motivi per cui sarebbe impossibile o non sarebbe una buona idea?

(Nota: non sto cercando una raccomandazione per la lingua: questa è una domanda sulla programmazione del linguaggio di programmazione. Mi piacerebbe sapere se questa idea ha un merito, e spero di imparare qualcosa sulle equivalenze tra mutevole e immutabile / costrutti funzionali. Non chiudere questa domanda come una domanda di "raccomandazione".)

    
posta jdm 26.06.2017 - 12:11
fonte

2 risposte

5

La cosa più vicina a ciò che stai chiedendo è probabilmente la notazione di Haskell, che ti consente di scrivere un codice che assomiglia sequenze di passi imperativi, quindi li traduce internamente in un'espressione monadica. Se usi una monade gratuita , puoi creare un interprete per quell'espressione che fa ciò che vuoi con esso.

Un'altra idea che si avvicina molto a ciò che stai chiedendo, in particolare la parte di nidificazione profonda, è obiettivi . Le lenti sono difficili da spiegare in modo conciso, ma hanno la stessa semantica di creare qualcosa che assomiglia a una mutazione imperativa per il programmatore, ma che esegue un'operazione pura sotto il cofano.

    
risposta data 26.06.2017 - 18:51
fonte
2

I wonder if somebody tried creating a language where you can write mutations in a straightforward imperative style, and the compiler transforms the changes automatically to pure, Redux-style actions. For example (pseudo-js):

atomic function repaint_car(car) {
    car.color = "Blue"
    car.value += 1000
}

Beh, questo non è esattamente questo, ma stato di Haskell- le famiglie di monad del trasformatore , meglio conosciute come le famiglie di% monade ST (e che non devono essere confuse con la famiglia monad State ), si avvicinano molto.

Con le famiglie monad ST , puoi scrivere le mutazioni in uno stile imperativo semplice e il risultato risulta essere un codice puramente funzionale. La cosa su ST è che il compilatore non lo fa trasformando il tuo codice in codice puramente funzionale; il codice sottostante è ancora imperativo e di stato. Il compilatore fornisce semplicemente un'interfaccia puramente funzionale a questo.

Quindi un modo per implementare la funzione sopra potrebbe essere ...

-- A car in a state thread "s" (a "Car s") consists of a string variable in
-- that thread (an "STRef s String") and an int variable in that thread
-- (an "STRef s Int").
data Car s = Car { carColor :: STRef s String, carValue :: STRef s Int }

-- Given a car in a state thread "s" (a "Car s"), we can perform
-- an action in that thread (an "ST s ()").
paintCarBlue :: Car s -> ST s ()
paintCarBlue car = do
    writeSTRef (carColor car) "Blue"
    modifySTRef (carValue car) (+ 1000)

Haskell tratta tutto il codice sopra come puramente funzionale: può essere chiamato da un altro codice puro senza la necessità di quel brutto marcatore IO o altro. Le famiglie di% monade% co_de utilizzano un po 'di inganno di sistema tipo per garantire che tutto sia deterministico e privo di effetti collaterali.

    
risposta data 26.06.2017 - 20:41
fonte