2010-07-19 58 views
8

我嘗試使用scalaz這樣定義讀者單子:讀者單子與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) 
} 

但我得到一個編譯器錯誤:

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

這是爲什麼?

感謝, 列維

回答

16

此錯誤是相當不透明的,甚至Scala的標準。以=結尾的方法名稱會被專門處理 - 它們首先被視爲正常標識符,如果失敗,則將其擴展爲自我賦值。

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 
     ^

如果你弄不清你的程序的語法解釋,它的運行scalac -Xprint:parser,看看發生了什麼事情是一個好主意。同樣,您可以使用-Xprint:typer-Xprint:jvm來查看程序轉換的後期階段。

那麼,您如何在您的Reader上撥打>>=?首先,您需要明確地將類型參數Env傳遞給env。必須將生成的Reader[Env, Env]轉換爲MA[M[_], A]。對於簡單類型的構造函數,隱式轉換MAs#ma就足夠了。然而,這兩個參數類型的構造函數Reader必須部分應用 - 這意味着它不能被推斷,而是你必須提供一個特定的隱式轉換。

如果Adriaan在implement higher-order unification for type constructor inference找到一個空閒的下午,情況就會大大改善。 :)

在此之前,這是您的代碼。還有幾條評論是內聯的。

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

謝謝。這就是訣竅。我必須承認,雖然Scala真的令我失望,當我嘗試使用它作爲函數式語言時,因爲它感覺像是一個巨大的黑客。 – 2010-07-19 21:12:42

+4

我想你是從哈斯克爾來的。斯卡拉不能與Hindley-Milner推論競爭,不強制純度,並且默認情況下是嚴格的。它確實有JVM互操作,隱式參數可以對類型類進行一些處理。 – retronym 2010-07-19 21:31:24