2010-07-19 13 views
8

je tente de définir le lecteur monade avec scalaz comme ceci:Monad avec 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) 
} 

mais je reçois une erreur de compilation:

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

Pourquoi?

Merci, Levi

Répondre

16

Cette erreur est assez opaque, même selon les normes de Scala. Les noms de méthode se terminant par = sont traités spécialement - ils sont d'abord considérés comme un identifiant normal, et à défaut, ils sont étendus à une auto-affectation.

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 vous êtes confus au sujet de l'interprétation syntaxique de votre programme, il est une bonne idée de courir scalac -Xprint:parser pour voir ce qui se passe. De même, vous pouvez utiliser -Xprint:typer ou -Xprint:jvm pour voir les phases ultérieures de la transformation du programme.

Alors, comment appelez-vous >>= sur votre Reader? Tout d'abord, vous devrez passer explicitement l'argument de type Env à env. Le résultat Reader[Env, Env] doit ensuite être converti en MA[M[_], A]. Pour les constructeurs de types simples, la conversion implicite MAs#ma suffira. Cependant, le constructeur de deux types de paramètres Reader doit être appliqué partiellement - cela signifie qu'il ne peut pas être déduit et que vous devez fournir une conversion implicite spécifique.

La situation serait grandement améliorée si Adriaan trouvait un après-midi libre au implement higher-order unification for type constructor inference. :)

D'ici là, voici votre code. Quelques autres commentaires sont en ligne.

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

Merci. Cela fait l'affaire. Je dois avouer cependant que Scala me déçoit vraiment quand j'essaye de l'utiliser comme un langage fonctionnel parce que c'est comme un hack gigantesque pour moi. –

+4

Vous venez d'Haskell, je présume. Scala ne peut pas rivaliser avec l'inférence Hindley-Milner, n'applique pas la pureté, et est stricte par défaut. Il a interopérabilité JVM, les paramètres implicites peuvent encoder quelques choses sont difficiles avec les classes de type. – retronym