2010-07-19 35 views
8

trato de definir la mónada Reader con scalaz así:lector Mónada con Scalaz

import scalaz._ 
import Scalaz._ 

final class Reader[E,A](private[Reader] val runReader: E => A) 

object Reader { 
    def apply[E,A](f: E => A) = new Reader[E,A](f) 
    def env[E]: Reader[E,E] = Reader(identity _) 

    implicit def ReaderMonad[E] = new Monad[PartialApply1Of2[Reader,E]#Apply] { 
    def pure[A](a: => A) = Reader(_ => a) 

    def bind[A,B](m: Reader[E,A], k: A => Reader[E,B]) = 
     Reader(e => k(m.runReader(e)).runReader(e)) 
    } 
} 


object Test { 
    import Reader._ 

    class Env(val s: String) 

    def post(s: String): Reader[Env, Option[String]] = 
    env >>= (e => if (e.s == s) some(s).pure else none.pure) 
} 

pero me da un error del compilador:

reader.scala:27: reassignment to val 
    env >>= (e => if (e.s == s) some(s).pure else none.pure) 
     ^

¿Por qué?

Gracias, Levi

Respuesta

16

Este error es bastante opaca, incluso para los estándares de Scala. Los nombres de métodos que terminan con = se tratan de manera especial: primero se los considera como un identificador normal y, en caso de que no se completen, se expanden a una autoasignación.

scala> def env[A] = 0 
env: [A]Int 

scala> env >>= 0 
<console>:7: error: reassignment to val 
     env >>= 0 
     ^

scala> env = env >> 0 
<console>:6: error: reassignment to val 
     env = env >> 0 
     ^

Si usted está confundido acerca de la interpretación sintáctica de su programa, que es una buena idea ejecutar scalac -Xprint:parser para ver lo que está pasando. Del mismo modo, puede usar -Xprint:typer o -Xprint:jvm para ver las fases posteriores de la transformación del programa.

Entonces, ¿cómo llama al >>= en su Reader? En primer lugar, deberá pasar explícitamente el argumento de tipo Env al env. El resultado Reader[Env, Env] se debe convertir a MA[M[_], A]. Para los constructores de tipo simple, la conversión implícita MAs#ma será suficiente. Sin embargo, el constructor de dos tipos param Reader debe aplicarse parcialmente, lo que significa que no se puede inferir y, en su lugar, debe proporcionar una conversión implícita específica.

La situación mejoraría enormemente si Adriaan alguna vez encuentra una tarde libre en implement higher-order unification for type constructor inference. :)

Hasta entonces, aquí está su código. Algunos comentarios más están en línea.

import scalaz._ 
import Scalaz._ 

final class Reader[E, A](private[Reader] val runReader: E => A) 

object Reader { 
    def apply[E, A](f: E => A) = new Reader[E, A](f) 

    def env[E]: Reader[E, E] = Reader(identity _) 

    implicit def ReaderMonad[E]: Monad[PartialApply1Of2[Reader, E]#Apply] = new Monad[PartialApply1Of2[Reader, E]#Apply] { 
    def pure[A](a: => A) = Reader(_ => a) 

    def bind[A, B](m: Reader[E, A], k: A => Reader[E, B]) = 
     Reader(e => k(m.runReader(e)).runReader(e)) 
    } 

    // No Higher Order Unification in Scala, so we need partially applied type constructors cannot be inferred. 
    // That's the main reason for defining function in Scalaz on MA, we can create one implicit conversion 
    // to extract the partially applied type constructor in the type parameter `M` of `MA[M[_], A]`. 
    // 
    // I'm in the habit of explicitly annotating the return types of implicit defs, it's not strictly necessary 
    // but there are a few corner cases it pays to avoid. 
    implicit def ReaderMA[E, A](r: Reader[E, A]): MA[PartialApply1Of2[Reader, E]#Apply, A] = ma[PartialApply1Of2[Reader, E]#Apply, A](r) 
} 


object Test { 
    import Reader._ 

    class Env(val s: String) 

    def post(s: String): Reader[Env, Option[String]] = 
    // Need to pass the type arg `Env` explicitly here. 
    env[Env] >>= {e => 
     // Intermediate value and type annotation not needed, just here for clarity. 
     val o: Option[String] = (e.s === s).guard[Option](s) 
     // Again, the partially applied type constructor can't be inferred, so we have to explicitly pass it. 
     o.pure[PartialApply1Of2[Reader, Env]#Apply] 
    } 
} 
+2

Gracias. Eso hace el truco. Debo confesar que Scala realmente me está decepcionando cuando lo intento, de hecho lo uso como un lenguaje funcional porque se siente como un gigantesco hack para mí. –

+4

Vienes de Haskell, supongo. Scala no puede competir con la inferencia de Hindley-Milner, no impone la pureza y es estricto por defecto. Tiene interoperabilidad JVM, los params implícitos pueden codificar algunas cosas que son difíciles con las clases de tipos. – retronym