2011-09-14 14 views
5

Esta es una continuación de la pregunta this.Ayúdeme a entender este código de Scala: Scalaz IO Monad e implica

Aquí está el código que estoy tratando de entender (que es de 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)() 
    } 
    } 
} 

Este código se utiliza como esto (estoy asumiendo un import io._ está implícito)

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 Ahora estoy tratando de entender la definición implicit val IOMonad. Así es como lo entiendo. Esto es scalaz.Monad, por lo que necesita definir pure y bind valores abstractos del rasgo scalaz.Monad.

pure toma un valor y lo convierte en un valor contenido en el tipo "contenedor". Por ejemplo, podría tomar un Int y devolver un List[Int]. Esto parece bastante simple.

bind toma un tipo "contenedor" y una función que asigna el tipo que el contenedor mantiene a otro tipo. El valor que se devuelve es el mismo tipo de contenedor, pero ahora contiene un nuevo tipo. Un ejemplo sería tomar un List[Int] y asignarlo a List[String] usando una función que asigna Int s a String s. ¿Es bind prácticamente lo mismo que map?

La implementación de bind es donde estoy atascado. Aquí está el código:

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)() 
} 

Esta definición tiene IO[A] y lo asigna a IO[B] utilizando una función que toma un A y devuelve un IO[B]. Supongo que para hacer esto, tiene que usar flatMap para "aplanar" el resultado (¿correcto?).

El = IO { ... } es lo mismo que

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

que pienso?

el método implicitly busca un valor implícito (valor, ¿no?) Que implementa Monad[Function0]. ¿De dónde viene esta definición implícita? Supongo que esto es de la definición implicit val IOMonad = new Monad[IO] {...}, pero estamos dentro de esa definición ahora y las cosas se vuelven un poco circulares y mi cerebro comienza a atascarse en un bucle infinito :)

Además, el primer argumento para bind (() => a.unsafePerformIO) parece ser una función que no toma parámetros y devuelve a.unsafePerformIO. ¿Cómo debería leer esto? bind toma un tipo de contenedor como primer argumento, por lo que ¿() => a.unsafePerformIO se resuelve en un tipo de contenedor?

+0

Scalaz en realidad ofrece una mónada IO fuera de la caja ahora. import scalaz.efectos._ – Apocalisp

Respuesta

14

IO[A] está destinado a representar una Action devolver un A, donde el resultado de la acción puede depender del medio ambiente (es decir, cualquier cosa, los valores de las variables, el sistema de archivos, la hora del sistema ...) y la ejecución de la acción puede también modifica el ambiente. En realidad, el tipo de scala para una Acción sería Function0. Function0[A] devuelve una A cuando se llama y se puede depender y modificar el entorno. IO es Function0 con otro nombre, pero está destinado a distinguir (¿etiqueta?) aquellas Function0 que dependen del entorno de las otras, que en realidad son valores puros (si dices que f es una función [A] que siempre devuelve el mismo valor, sin ningún efecto secundario, no hay mucha diferencia entre f y su resultado). Para ser precisos, no es tanto que la función etiquetada como IO deba tener un efecto secundario. Es que aquellos que no están etiquetados no deben tener ninguno. Sin embargo, tenga en cuenta que, al envolver funciones impuras en IO, es totalmente voluntario, de ninguna manera tendrá garantía cuando obtenga un Function0 que sea puro. El uso de IO ciertamente no es el estilo dominante en scala.

pure toma un valor y lo convierte en un valor contenido en el tipo "contenedor" .

Muy bien, pero "contenedor" puede significar un montón de cosas. Y el devuelto por puro debe ser lo más liviano posible, debe ser el que no haga diferencia. El punto de la lista es que pueden tener cualquier cantidad de valores. El devuelto por puro debe tener uno. El punto de IO es que depende y afecta el medio ambiente. El devuelto por puro no debe hacer tal cosa. Entonces, en realidad es el Function0 puro () => a, envuelto en IO.

se unen más o menos el mismo que el mapa

No es así, se unen es el mismo que flatMap. A medida que escribe, mapa recibiría una función Int-String, pero aquí tienen la función Int-List[String]

Ahora, olvida IO por un momento y considere lo que bind/flatMap significaría para una acción, es decir, para Function0. Tengamos

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

Ahora bien, si tenemos que combinar, como bind/flatMap hace, esos elementos para conseguir una acción que devuelve una cadena, lo que debe ser es bastante claro: pedir al lector por el número de línea, leer esa línea y la devuelve. Eso sería

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

Más genéricamente

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

y más corto:

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

así que sabemos lo bind debe ser para Function0, pure es clara también. Podemos hacer

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

Ahora, IO es Function0 in disguise. En lugar de simplemente hacer a(), debemos hacer a.unsafePerformIO. Y definir una, en lugar de () => body, escribimos IO {body} lo que podría haber

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} 
} 

En mi opinión, eso sería lo suficientemente bueno. Pero, de hecho, repite ActionMonad. El punto en el código al que se refiere es evitar eso y reutilizar lo que se hace para Function0.Uno va fácilmente de IO a Function0 (con () => io.unsafePerformIo) y también de Function0 a IO (con IO { action() }). Si tiene f: A => IO [B], también puede cambiarlo a f: A => Function0[B], solo compilando con la transformada IO a Function0, entonces (x: A) => f(x).unsafePerformIO.

lo que sucede aquí en el vínculo de la IO es:

  1. () => a.unsafePerformIO: girar a en un Function0
  2. (x:A) =>() => f(x).unsafePerformIO): girar f en una A =>Function0[B]
  3. implícitamente [mónada [Function0]] : obtenga la mónada predeterminada para Function0, la misma que la ActionMonad anterior
  4. bind(...): aplique la bind de la Function0 mónada a los argumentos a y f que acaban de ser convertido a Function0
  5. El encerrando IO{...}: convertir el resultado de nuevo a IO.

(No estoy seguro de que me gustan mucho)

+0

¡Muchas gracias! Voy a leer esto acerca de 5x –

+0

¿Cómo leería esto 'val readingLineAt: Int => Function0 [String] = Int => String {i: Int =>() => ...}'? P.ej. "readingLineAt" es un valor que devuelve un método que toma un Int y devuelve una Function0 [String]. Puedo llegar tan lejos, pero no estoy seguro de la implementación. es la 'Int => String' una especie de función anónima? –

+0

Lo sentimos, mal escrito/mal copiado/mal hecho. Corregido –

Cuestiones relacionadas