Domanda

Ecco un po 'di cibo per il pensiero.

Quando scrivo codice monadico, la monade impone l'ordinazione sulle operazioni fatte. Ad esempio, se scrivo nella monade IO:

do a <- doSomething
   b <- doSomethingElse
   return (a + b)

lo so doSomething sarà eseguito prima doSomethingElse.

Ora, considera il codice equivalente in una lingua come C:

return (doSomething() + doSomethingElse());

La semantica di C non specifica effettivamente quale ordine verranno valutate queste due chiamate di funzione, quindi il compilatore è libero di spostare le cose mentre fa piacere.

La mia domanda è questa: Come scriverei un codice monadico in Haskell che lascia anche questo ordine di valutazione? Idealmente, trarrei alcuni vantaggi quando l'ottimizzatore del mio compilatore guarda il codice e inizierei a muovere le cose.

Ecco alcune possibili tecniche che non svolgono il lavoro, ma sono nello "spirito" giusto:

  • Scrivi il codice in stile funttoriale, cioè, scrivi plus doSomething doSomethingElse e lascia plus Pianifica le chiamate monadiche. Inconvenienti: perdi la condivisione sui risultati delle azioni monadiche e plus Prende comunque una decisione su quando le cose finiscono per essere valutate.
  • Uso pigro io, questo è, unsafeInterleaveIO, che difende la programmazione alle esigenze pigre della valutazione. Ma Lazy è diverso da severo con ordine indefinito: in particolare voglio che tutte le mie azioni monadiche vengano eseguite.
  • Io pigro, combinato immediatamente con tutti gli argomenti. In particolare, seq non impone vincoli di ordinazione.

In questo senso, voglio qualcosa di più flessibile dell'ordinamento monadico ma meno flessibile della pigrizia piena.

È stato utile?

Soluzione

Questo problema del codice monade eccessivo è noto come il "Problema di monadi commutativi".

Le monadi commutative sono monadi per le quali l'ordine delle azioni non fa alcuna differenza (si spostano), cioè quando segue il codice:

do a <- f x
   b <- g y
   m a b

equivale a:

do b <- g y
   a <- f x
   m a b

Ci sono molte monadi che si spostano (ad esempio Maybe, Random). Se la monade è commutativa, ad esempio le operazioni catturate al suo interno possono essere calcolate in parallelo. Sono molto utili!

Tuttavia, noi Non avere una buona sintassi per le monadi che si spostano, anche se Molte persone hanno chiesto Per una cosa del genere - è ancora un Problema di ricerca aperta.

A parte questo, i funttori applicativi ci danno tale libertà di riordinare i calcoli, tuttavia, devi rinunciare alla nozione di bind (Come suggerimenti per EG liftM2 mostrare).

Altri suggerimenti

Questo è un hack profondo sporco, ma sembra che dovrebbe farmi il trucco.

{-# OPTIONS_GHC -fglasgow-exts #-}
{-# LANGUAGE MagicHash #-}
module Unorder where
import GHC.Types

unorder :: IO a -> IO b -> IO (a, b)
unorder (IO f) (IO g) = IO $ \rw# ->
           let (# _, x #) = f rw#
               (# _, y #) = g rw#
           in (# rw# , (x,y) #)

Dal momento che ciò pone il non determinismo nelle mani del compilatore, dovrebbe comportarsi "correttamente" (cioè non molto tempo) per quanto riguarda anche i problemi di flusso (cioè le eccezioni).

D'altra parte, non possiamo tirare lo stesso trucco più monadi standard come State e Either a Dal momento che facciamo davvero affidamento sull'azione spettrale a distanza disponibile tramite disordine con il RealWorld gettone. Per ottenere il comportamento giusto, avremmo bisogno di un po 'di annotazione a disposizione dell'ottimizzatore che indicava che stavamo bene con una scelta non deterministica tra due alternative non equivalenti.

La semantica di C non specifica effettivamente quale ordine verranno valutate queste due chiamate di funzione, quindi il compilatore è libero di spostare le cose mentre fa piacere.

Ma e se doSomething() provoca un effetto collaterale che cambierà il comportamento di doSomethingElse()? Vuoi davvero che il compilatore possa fare casino con l'ordine? (Suggerimento: no) Il fatto che tu sia in una monade suggerisce che tale può essere il caso. La tua nota che "perdi la condivisione sui risultati" suggerisce anche questo.

Tuttavia, si noti che il monadico non significa sempre sequenziato. Non è esattamente quello che descrivi, ma potresti essere interessato al Par monade, che ti consente di eseguire le tue azioni in parallelo.

Sei interessato a lasciare l'ordine non definito in modo che il compilatore possa ottimizzarlo magicamente per te. Forse invece dovresti usare qualcosa come il par monade per indicare le dipendenze (alcune cose inevitabilmente devono accadere prima degli altri) e quindi lasciare che il resto funzionasse in parallelo.

Nota a margine: non confondere Haskell return Essere qualcosa di simile a C return

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top