2011-09-14 20 views
5

Questo è un seguito alla domanda this.Aiutami a capire questo codice Scala: scalaz IO Monade e impliciti

Ecco il codice che sto cercando di capire (è da http://apocalisp.wordpress.com/2010/10/17/scalaz-tutorial-enumeration-based-io-with-iteratees/):

object io { 
    sealed trait IO[A] { 
    def unsafePerformIO: A 
    } 

    object IO { 
    def apply[A](a: => A): IO[A] = new IO[A] { 
     def unsafePerformIO = a 
    } 
    } 

    implicit val IOMonad = new Monad[IO] { 
    def pure[A](a: => A): IO[A] = IO(a) 
    def bind[A,B](a: IO[A], f: A => IO[B]): IO[B] = IO { 
     implicitly[Monad[Function0]].bind(() => a.unsafePerformIO, 
             (x:A) =>() => f(x).unsafePerformIO)() 
    } 
    } 
} 

Questo codice viene utilizzato come questo (sto assumendo un import io._ è implicito)

def bufferFile(f: File) = IO { new BufferedReader(new FileReader(f)) } 

def closeReader(r: Reader) = IO { r.close } 

def bracket[A,B,C](init: IO[A], fin: A => IO[B], body: A => IO[C]): IO[C] = for { a <- init 
     c <- body(a) 
     _ <- fin(a) } yield c 

def enumFile[A](f: File, i: IterV[String, A]): IO[IterV[String, A]] = bracket(bufferFile(f), 
      closeReader(_:BufferedReader), 
      enumReader(_:BufferedReader, i)) 

I ora sto cercando di capire la definizione implicit val IOMonad. Ecco come lo capisco. Questo è un scalaz.Monad, quindi è necessario definire i valori astratti pure e bind del tratto scalaz.Monad.

pure prende un valore e lo trasforma in un valore contenuto nel tipo "contenitore". Ad esempio potrebbe richiedere un Int e restituire un List[Int]. Questo sembra piuttosto semplice.

bind accetta un tipo "contenitore" e una funzione che associa il tipo che il contenitore conserva a un altro tipo. Il valore restituito è lo stesso tipo di contenitore, ma ora contiene un nuovo tipo. Un esempio sarebbe prendere uno List[Int] e mapparlo a un List[String] utilizzando una funzione che mappa Int s a String s. È bind più o meno come map?

L'implementazione di bind è il punto in cui sono bloccato. Ecco il codice:

def bind[A,B](a: IO[A], f: A => IO[B]): IO[B] = IO { 
    implicitly[Monad[Function0]].bind(() => a.unsafePerformIO, 
     (x:A) =>() => f(x).unsafePerformIO)() 
} 

Questa definizione prende IO[A] e la mappa per IO[B] utilizzando una funzione che prende un A e restituisce un IO[B]. Immagino di farlo, deve usare flatMap per "appiattire" il risultato (corretto?).

Il = IO { ... } è lo stesso di

= new IO[A] { 
    def unsafePerformIO = implicitly[Monad[Function0]].bind(() => a.unsafePerformIO, 
     (x:A) =>() => f(x).unsafePerformIO)() 
    } 
} 

penso?

il metodo implicitly cerca un valore implicito (valore, giusto?) Che implementa Monad[Function0]. Da dove viene questa definizione implicita? Immagino che questo sia dalla definizione implicit val IOMonad = new Monad[IO] {...}, ma al momento siamo all'interno di quella definizione e le cose diventano un po 'circolari e il mio cervello inizia a rimanere bloccato in un ciclo infinito :)

Inoltre, il primo argomento a bind (() => a.unsafePerformIO) sembra essere una funzione che non accetta parametri e restituisce a.unsafePerformIO. Come dovrei leggere questo? bind accetta un tipo di contenitore come primo argomento, quindi forse () => a.unsafePerformIO si risolve in un tipo di contenitore?

+0

Scalaz fornisce effettivamente una monade IO fuori dalla scatola ora. import scalaz.effects._ – Apocalisp

risposta

14

IO[A] è destinato a rappresentare un Action restituzione di un A, dove il risultato dell'azione può dipendere l'ambiente (che significa niente, i valori delle variabili, file system, ora di sistema ...) e l'esecuzione dell'azione può anche modificare l'ambiente. In realtà, il tipo scala per un'azione sarebbe Function0. Function0[A] restituisce un A quando viene chiamato ed è certamente consentito dipendere e modificare l'ambiente. IO è Function0 con un altro nome, ma è destinato a distinguere (tag?) quelle Function0 che dipendono dall'ambiente dagli altri, che sono in realtà un valore puro (se dici f è una funzione [A] che restituisce sempre lo stesso valore, senza alcun effetto collaterale, non c'è molta differenza tra f e il suo risultato). Per essere precisi, non è tanto che la funzione taggata come IO deve avere un effetto collaterale. È che quelli non così etichettati non ne devono avere. Nota comunque che il wrapping delle funzioni impure in IO è interamente volontario, non c'è modo di avere una garanzia quando ottieni un Function0 che è puro. L'utilizzo di IO non è certamente lo stile dominante in scala.

puro prende un valore e lo trasforma in un valore contenuto nel tipo "contenitore" .

Giusto, ma "contenitore" può significare un bel po 'di cose. E quello restituito da puro deve essere il più leggero possibile, deve essere quello che non fa differenza. Il punto di lista è che possono avere un numero qualsiasi di valori. Quello restituito dal puro deve averne uno. Il punto di IO è che dipende e influisce sull'ambiente. Quello restituito da puro non deve fare nulla del genere. Quindi è in realtà il () => a puro, avvolto in IO.

legano più o meno la stessa di mappa

Non è così, si legano è lo stesso di flatMap. Come si scrive, mappa riceverebbe una funzione Int-String, ma qui si ha la funzione di Int-List[String]

Ora, dimentica IO per un attimo e considerare ciò che si legano/flatMap significherebbe per un'azione, cioè per Function0. Diamo

val askUserForLineNumber:() => Int = {...} 
val readingLineAt: Int => Function0[String] = {i: Int =>() => ...} 

Ora, se dobbiamo unire, come bind/flatMap fa, gli elementi per ottenere un'azione che restituisce una stringa, che cosa deve essere è abbastanza chiaro: chiedere al lettore per il numero di riga, leggere quella linea e la restituisce. Sarebbe

val askForLineNumberAndReadIt=() => { 
    val lineNumber : Int = askUserForLineNumber() 
    val readingRequiredLine: Function0[String] = readingLineAt(line) 
    val lineContent= readingRequiredLine() 
    lineContent 
} 

più genericamente

def bind[A,B](a: Function0[A], f: A => Function0[B]) =() => { 
    val value = a() 
    val nextAction = f(value) 
    val result = nextAction() 
    result 
} 

e più breve:

def bind[A,B](a: Function0[A], f: A => Function0[B]) 
    =() => {f(a())()} 

quindi sappiamo cosa bind deve essere per Function0, pure è chiaro anche. Possiamo fare

object ActionMonad extends Monad[Function0] { 
    def pure[A](a: => A) =() => a 
    def bind[A,B](a:() => A, f: A => Function0[B]) =() => f(a())() 
} 

Ora, IO è Function0 travestito. Invece di fare solo a(), dobbiamo fare a.unsafePerformIO. E per definire uno, invece di () => body, scriviamo IO {body} Quindi ci potrebbe essere

object IOMonad extends Monad[IO] { 
    def pure[A](a: => A) = IO {a} 
    def bind[A,B](a: IO[A], f: A => IO[B]) = IO {f(a.unsafePerformIO).unsafePerformIO} 
} 

A mio avviso, che sarebbe abbastanza buono. Ma in effetti ripete ActionMonad. Il punto nel codice a cui fai riferimento è quello di evitarlo e riutilizzare invece quello che viene fatto per Function0.Uno va facilmente da IO a (con () => io.unsafePerformIo) e da Function0 a IO (con IO { action() }). Se hai f: A => IO [B], puoi anche modificarlo in f: A => Function0[B], semplicemente componendo con la trasformazione IO in Function0, quindi (x: A) => f(x).unsafePerformIO.

Cosa succede qui nel vicolo cieco di IO è:

  1. () => a.unsafePerformIO: trasformare a in un Function0
  2. (x:A) =>() => f(x).unsafePerformIO): trasformare f in un A =>Function0[B]
  3. implicitamente [Monade [Function0]] : ottieni la monade predefinita per Function0, esattamente uguale a ActionMonad sopra
  4. bind(...): applica lo bind del Function0 Monade agli argomenti a e f che sono appena stati convertiti in Function0
  5. Il allegando IO{...}: convertire il risultato al IO.

(Non sono sicuro mi piace molto)

+0

Grazie mille! Vado a leggere questo 5x –

+0

Come potrei leggere questo 'val readingLineAt: Int => Function0 [String] = Int => String {i: Int =>() => ...}'? Per esempio. "readingLineAt" è un valore che restituisce un metodo che accetta un Int e restituisce una Function0 [String]. Posso arrivare così lontano, ma non sono sicuro dell'attuazione. la 'Int => String' è una sorta di funzione anonima? –

+0

Spiacente, in errore/errato/non corretto. Risolto –

Problemi correlati