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]
}
}
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. –
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