2015-03-27 7 views
6

Uso una monade IO standard.Come implementare un cortocircuito con MONAD IO in Scala

E a un certo punto, ho bisogno di cortocircuito. In una determinata condizione, non voglio eseguire i seguenti iOS.

Ecco la mia soluzione, ma ho trovato troppo prolisso e non elegante:

def shortCircuit[A](io: IO[A], continue: Boolean) = 
    io.map(a => if (continue) Some(a) else None) 

    for { 
    a <- io 
    b <- shortCircuit(io, a == 1) 
    c <- shortCircuit(io, b.map(_ == 1).getOrElse(false)) 
    d <- shortCircuit(io, b.map(_ == 1).getOrElse(false)) 
    e <- shortCircuit(io, b.map(_ == 1).getOrElse(false)) 
    } yield … 

Ad esempio, per il 3 °, 4 ° e 5 ° linea, ho bisogno di ripetere la stessa condizione.

C'è un modo migliore?

+2

Hai bisogno dei valori di 'a',' b', 'c', ecc., O ti interessano solo gli effetti? 'OptionT [IO,?]' Tipo di suoni come quello che stai cercando, ma ti darebbe un 'None 'se questo cortocircuiti. –

+0

Ho bisogno di questi valori: come condizione per il cortocircuito e anche nella clausola yield. –

+0

io ha un effetto collaterale, quindi il metodo run può restituire un valore diverso dopo ogni chiamata. –

risposta

1

In realtà non avete cortocircuitato nulla. Stai ancora eseguendo gli IO; semplicemente non catturi i valori.

Inoltre, la monade IO standard non definisce filter (o withFilter), quindi non è possibile utilizzare le protezioni nella propria comprensione.

Ora, se si desidera solo quello che hai detto (stessa logica, solo più DRY), si può sempre assegnare una variabile temporanea nella per la comprensione:

for { 
    a <- io 
    b <- shortCircuit(io, a == 1) 
    continue = b.map(_ == 1).getOrElse(false) 
    c <- shortCircuit(io, continue) 
    d <- shortCircuit(io, continue) 
    e <- shortCircuit(io, continue) 
} yield … 

Ma se si vuole realmente breve -circuito, dovrai rompere i casi in qualche modo. Ecco una possibilità, supponendo che si desidera solo per impacchettare tutto in un array in modo che il tipo di ritorno è semplice, e il tuo compagno oggetto IO ha un metodo di applicazione che è possibile utilizzare per creare qualcosa che solo restituisce un valore:

io.flatMap(a => 
    if (a == 1) IO(() => Array(a)) 
    else io.flatMap(b => 
    if (b == 1) IO(() => Array(a,b)) 
    else for { 
     c <- io 
     d <- io 
     e <- io 
    } yield Array(a,b,c,d,e) 
) 
) 

Se i tipi di reso sono più complicati, potrebbe essere necessario lavorare di più con i tipi di specifica.

FWIW, vale la pena notare la sanzione che si paga per mantenere le cose avvolte nelle monadi; senza, la stessa logica sarebbe (per esempio):

io() match { 
    case 1 => Array(1) 
    case a => io() match { 
    case 1 => Array(a, 1) 
    case b => Array(a, b, io(), io(), io()) 
    } 
} 

E se permettete ritorni, si ottiene:

val a = io() 
if (a == 1) return Array(a) 
val b = io() 
if (b == 1) return Array(a, b) 
Array(a, b, io(), io(), io()) 

E 'anche possibile in linea di principio per decorare la monade IO con metodi aggiuntivi che aiutano un po ', ma lo standard withFilter non funzionerà, quindi non sarà possibile utilizzare lo zucchero sintattico per la comprensione.

+0

Puoi spiegare perché 'withFilter' non funzionerà? –

+0

Wdyt sull'aggiunta di questo metodo su IO: def conGuard [B] (f: A => IO [B], guardia: A => Booleano, costruttore: A => B): IO [B] = nuovo IO [ B] { def gestita = { val a = self.run se (guardia (a)) f (a) .run altro costruttore (a) }} –

+0

@YannMoisan - Beh, in realtà dipende la firma dei metodi. Il cortocircuito anticipato tende a cambiare la firma del metodo, che non si può fare con il filtro.Non sono sicuro a colpo d'occhio se 'withGuard' funzionerà, ma non è molto difficile scoprire se fai il tuo IO essere stampato e provalo! –