2010-07-19 1 views
8

cerco di definire la monade Reader con scalaz come questo:Reader Monad 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) 
} 

ma ottengo un errore di compilazione:

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

Perché?

Grazie, Levi

risposta

16

Questo errore è abbastanza opaca, anche per gli standard di Scala. I nomi dei metodi che terminano con = vengono trattati in modo speciale: vengono prima considerati come un identificatore normale e, in caso contrario, vengono estesi a un'assegnazione automatica.

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 
     ^

Se siete confusi circa l'interpretazione sintattica del programma, è una buona idea eseguire scalac -Xprint:parser per vedere cosa sta succedendo. Allo stesso modo, è possibile utilizzare -Xprint:typer o -Xprint:jvm per vedere le fasi successive della trasformazione del programma.

Quindi, come si chiama >>= sul Reader? Prima di tutto, è necessario passare esplicitamente l'argomento di tipo Env a env. Il risultato Reader[Env, Env] deve quindi essere convertito in MA[M[_], A]. Per i costruttori di tipi semplici, la conversione implicita MAs#ma sarà sufficiente. Tuttavia, il costruttore di due tipi param Reader deve essere parzialmente applicato - questo significa che non può essere dedotto e invece è necessario fornire una conversione implicita specifica.

La situazione sarebbe notevolmente migliorata se Adriaan dovesse mai trovare un pomeriggio libero a implement higher-order unification for type constructor inference. :)

Fino ad allora, ecco il tuo codice. Alcuni altri commenti sono in linea.

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

Grazie. Questo fa il trucco. Devo confessare che Scala mi sta davvero deludendo quando provo a usarlo come linguaggio funzionale perché mi sembra un gigantesco trucco. –

+4

Stai venendo da Haskell, presumo. Scala non può competere con l'inferenza di Hindley-Milner, non impone la purezza ed è severa di default. Ha l'interoperabilità JVM, i parametri impliciti possono codificare alcune cose difficili con le classi di tipi. – retronym