2011-09-14 60 views
5

这是this的后续问题。帮我理解这个Scala代码:scalaz IO Monad和implicits

这里是我想了解(这是从http://apocalisp.wordpress.com/2010/10/17/scalaz-tutorial-enumeration-based-io-with-iteratees/)代码:

object io { 
    sealed trait IO[A] { 
    def unsafePerformIO: A 
    } 

    object IO { 
    def apply[A](a: => A): IO[A] = new IO[A] { 
     def unsafePerformIO = a 
    } 
    } 

    implicit val IOMonad = new Monad[IO] { 
    def pure[A](a: => A): IO[A] = IO(a) 
    def bind[A,B](a: IO[A], f: A => IO[B]): IO[B] = IO { 
     implicitly[Monad[Function0]].bind(() => a.unsafePerformIO, 
             (x:A) =>() => f(x).unsafePerformIO)() 
    } 
    } 
} 

这段代码是用来像这样(我假设的import io._是暗示)

def bufferFile(f: File) = IO { new BufferedReader(new FileReader(f)) } 

def closeReader(r: Reader) = IO { r.close } 

def bracket[A,B,C](init: IO[A], fin: A => IO[B], body: A => IO[C]): IO[C] = for { a <- init 
     c <- body(a) 
     _ <- fin(a) } yield c 

def enumFile[A](f: File, i: IterV[String, A]): IO[IterV[String, A]] = bracket(bufferFile(f), 
      closeReader(_:BufferedReader), 
      enumReader(_:BufferedReader, i)) 

我现在试图了解implicit val IOMonad的定义。这是我的理解。这是一个scalaz.Monad,所以它需要定义scalaz.Monad特征的purebind抽象值。

pure获取一个值并将其转换为“容器”类型中包含的值。例如,它可能需要Int并返回List[Int]。这似乎很简单。

bind需要一个“容器”类型和一个将容器容纳的类型映射到另一个类型的函数。返回的值是相同的容器类型,但它现在保存一个新类型。一个例子是采用List[Int]并使用映射Int s到String s的函数将其映射到List[String]bindmap差不多吗?

bind的执行是我卡住的地方。下面的代码:

def bind[A,B](a: IO[A], f: A => IO[B]): IO[B] = IO { 
    implicitly[Monad[Function0]].bind(() => a.unsafePerformIO, 
     (x:A) =>() => f(x).unsafePerformIO)() 
} 

这个定义需要IO[A],并使用一个函数,它的A,并返回一个IO[B]它映射到IO[B]。我想这样做,它必须使用flatMap来“扁平”结果(正确?)。

= IO { ... }相同

= new IO[A] { 
    def unsafePerformIO = implicitly[Monad[Function0]].bind(() => a.unsafePerformIO, 
     (x:A) =>() => f(x).unsafePerformIO)() 
    } 
} 

我觉得呢?

implicitly方法寻找实现Monad[Function0]的隐式值(值,对吧?)。这个隐式定义来自哪里?我猜这是从implicit val IOMonad = new Monad[IO] {...}定义,但我们这个定义里面,现在,事情变得有点圆,我的大脑开始陷入无限循环:)

此外,第一个参数bind() => a.unsafePerformIO)似乎是一个不带参数并返回a.unsafePerformIO的函数。我应该怎么读这个? bind将容器类型作为其第一个参数,因此可能() => a.unsafePerformIO解析为容器类型?

+0

Scalaz实际开箱提供了一个IO单子了。进口scalaz.effects._ – Apocalisp

回答

14

IO[A]旨在表示Action返回一个A,其中行动的结果可能取决于环境(意思是什么,变量,文件系统,系统时间...值)和行动的执行可能也修改环境。实际上,Action的scala类型为Function0Function0[A]在被调用时返回一个A,并且肯定允许依赖和修改环境。 IOFunction0的另一个名称,但它的目的是区分(标签?)那些依赖于其他环境的函数0,它们实际上是纯粹的值(如果你说f是一个函数[A],它总是返回相同的值,没有任何副作用,那么f和它之间没有太大的区别结果)。确切地说,标记为IO的函数不会有太多副作用。那些没有如此标签的人一定没有。不过要注意的是,在IO中打包不纯的功能完全是自愿的,当你得到一个纯粹的Function0时,你不可能有任何保证。 使用IO当然不是scala中的主导风格。

pure获取一个值并将其转换为包含在 “容器”类型中的值。

相当正确,但“容器”可能意味着很多事情。而由纯粹返回的人必须尽可能轻,它一定是没有区别的人。列表的要点是他们可能有任何数量的值。纯粹返回的人必须有一个。 IO的关键在于它依赖并影响环境。纯粹返回的人不会做这样的事情。所以它实际上是纯粹的Function0() => a,包裹在IO

绑定几乎相同的地图

事实并非如此,绑定是一样的flatMap。当你写,地图将获得一个功能从IntString,但在这里,你必须从IntList[String]

功能现在,忘记IO片刻,考虑一下绑定/ flatMap将意味着一个动作,那就是为Function0。 让我们

val askUserForLineNumber:() => Int = {...} 
val readingLineAt: Int => Function0[String] = {i: Int =>() => ...} 

现在,如果我们一定要结合,作为绑定/ flatMap确实,这些项目得到一个返回字符串的操作,它必须是什么是很清楚的:索要行号的读者,读该行并返回它。这将是

val askForLineNumberAndReadIt=() => { 
    val lineNumber : Int = askUserForLineNumber() 
    val readingRequiredLine: Function0[String] = readingLineAt(line) 
    val lineContent= readingRequiredLine() 
    lineContent 
} 

更一般

def bind[A,B](a: Function0[A], f: A => Function0[B]) =() => { 
    val value = a() 
    val nextAction = f(value) 
    val result = nextAction() 
    result 
} 

而短:

def bind[A,B](a: Function0[A], f: A => Function0[B]) 
    =() => {f(a())()} 

因此,我们知道什么bind必须是Function0pure清晰了。我们可以做

object ActionMonad extends Monad[Function0] { 
    def pure[A](a: => A) =() => a 
    def bind[A,B](a:() => A, f: A => Function0[B]) =() => f(a())() 
} 

现在,IO是变相的Function0。我们必须做a.unsafePerformIO,而不是仅仅做a(),。并定义一个,而不是() => body,我们写IO {body} 所以有可能是

object IOMonad extends Monad[IO] { 
    def pure[A](a: => A) = IO {a} 
    def bind[A,B](a: IO[A], f: A => IO[B]) = IO {f(a.unsafePerformIO).unsafePerformIO} 
} 

在我看来,这将是足够好的。但实际上它重复了ActionMonad。您提到的代码中的一点是避免这一点,并重复使用Function0。人去轻松地从IOFunction0(与() => io.unsafePerformIo)以及来自Function0IO(与IO { action() })。如果已F:A => IO [B],还可以改变,要f: A => Function0[B],只是通过与IOFunction0构成变换,所以(x: A) => f(x).unsafePerformIO

这里发生在IO的绑定的是:

  1. () => a.unsafePerformIO:转aFunction0
  2. (x:A) =>() => f(x).unsafePerformIO):转f成A =>Function0[B]
  3. 隐式[单子[Function0]] :得到默认单子为Function0,与上面的ActionMonad非常相似
  4. bind(...):将bindFunction0 monad与参数af刚刚转换为Function0
  5. 封闭IO{...}:将结果转换回IO

(不知道我喜欢它了)

+0

非常感谢!我要读这约5x –

+0

我该怎么读'val readingLineAt:Int => Function0 [String] = Int => String {i:Int =>()=> ...}?例如。 “readingLineAt”是返回接受一个int并返回一个Function0 [字符串]的方法的值。我可以得到这么多,但我不确定实施。是'Int => String'某种匿名函数? –

+0

对不起,输入错误/ miscopied/mispasted。固定 –