Come la programmazione funzionale affronta le operazioni di incremento / decremento contemporanee invocate da utenti diversi?

0

Usando il linguaggio funzionale, come possono 2 parti differenti ottenere simultaneamente il risultato delle operazioni di incremento / decremento?

Per lo scenario seguente, Diciamo, ho 2 quantità in magazzino e 2 utenti in blocco di siti e-commerce 1 quantità ciascuno contemporaneamente. Come posso contrassegnare lo stock rimanente come "0" in FP, dato che lo stato è condiviso e immutabile? Suppongo che ogni flusso ottenga la copia dello stock iniziale '2' e funzioni su. In tal caso, come raggiungerà "0" a meno che non sia serializzato?

Descrivi gentilmente questo con un codice di esempio.

Qual è la differenza rispetto alle operazioni CAS su AtomicInteger (in JAVA) in cui si prevede che il client riproverà in caso di errore? (Sia in termini di approccio che di efficienza)

    
posta Vicky 03.01.2018 - 10:35
fonte

2 risposte

7

In Haskell, un approccio possibile è utilizzare la memoria transazionale del software per questo.

-- silly example mixing IO and STM

doOperation :: TVar Int -> IO ()
doOperation x = do -- IO monad
   putStrLn "Incrementing value!"
   atomically $ do -- STM monad
      v <- readTVar x
      -- putStrLn "We can't do I/O here, in the middle of STM!"
      -- print v
      writeTVar x $! v+1
   putStrLn "Incrementing complete."

Usando atomically , possiamo eseguire azioni STM nel mezzo delle azioni IO. Questo ci consente di leggere e scrivere x , quindi di incrementarlo. In alternativa, modifyTVar' (+1) farebbe anche l'incremento più convenientemente, ma soprattutto volevo separare la lettura dalla scrittura.

Per confronto, non c'è modo di eseguire azioni IO nel mezzo della transazione atomica STM. Se proviamo a decommentare le azioni IO nel codice sopra, il compilatore genera un errore di tipo perché IO non è STM . Questo è importante, dal momento che non dobbiamo stampare v nel mezzo di una transazione che potrebbe ancora essere ripristinata - non possiamo annullare print v , o in generale altri calcoli di I / O che potrebbero anche usare il disco o il Rete. (Gli sviluppatori di GHC usano launchMissiles come esempio archetipo di IO con effetti collaterali internazionali, senza "annulla" possibile.)

Operativamente, l'implementazione del monad STM tiene traccia delle modifiche alla variabile x . Durante la lettura, registra il vecchio valore di x (nessun blocco in questo momento). Durante la scrittura, anche il nuovo valore viene registrato, ma non scritto (nessun blocco). Letture successive all'interno della transazione leggono il nuovo valore di x (nessun blocco).

Alla fine della transazione, entriamo nella fase di commit: blocchiamo tutte le variabili coinvolte (usando un dead lock free ordering). Quindi testiamo se contengono ancora i vecchi valori. In caso contrario, eseguiamo il rollback della transazione e riproviamo. Se lo fanno, scriviamo i nuovi valori (dal log), sblocchiamo le variabili e abbiamo finito.

STM non è efficiente quanto gli algoritmi personalizzati per l'esclusione reciproca / l'esclusione dal deadlock che possono essere utilizzati per problemi specifici. È tuttavia molto generale e, a differenza delle serrature di base, molto componibile e flessibile. Pure FP rende l'approccio più sicuro controllando che il programmatore utilizzi solo operazioni sicure nelle transazioni (senza IO).

    
risposta data 03.01.2018 - 14:32
fonte
6

Ti stai chiedendo in che modo i linguaggi funzionali gestiscono lo stato mutabile condiviso. I linguaggi funzionali preferiscono e incoraggiano strutture di dati immutabili, ma tutti (AFAIK) hanno disposizioni per lo stato condiviso mutabile. Esiste una varietà di approcci, proprio come per le lingue imperative e OO, ma nel caso di un inventario lo stato sarebbe tipicamente un database, che gestirà la concorrenza e il blocco.

Un linguaggio FP in esecuzione sulla JVM (ad esempio Clojure) può utilizzare AtomicInteger proprio come Java.

Per i linguaggi funzionali pure è un po 'più complicato poiché un linguaggio puro in linea di principio non consente alcun tipo di stato mutabile. In Haskell questo significa solo operazioni sullo stato mutabile condiviso che si verificano nella monade IO, indicando che tali operazioni non sono pure.

In breve, non c'è nulla di magico in FP, e non c'è un pranzo gratis: non puoi avere purezza e stato mutevole condiviso allo stesso tempo.

    
risposta data 03.01.2018 - 13:30
fonte