这是this问题的后续措施。
这是我想要理解的代码(来自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,因此需要定义pure
特性的bind
和scalaz.Monad
抽象值。pure
接受一个值并将其转换为“容器”类型中包含的值。例如,它可能需要一个Int
并返回一个List[Int]
。这似乎很简单。bind
采用“容器”类型和将容器保存的类型映射为另一种类型的函数。返回的值是相同的容器类型,但是现在保留了新的类型。一个示例是获取List[Int]
并使用将List[String]
映射到Int
的函数将其映射到String
。 bind
与map
几乎一样吗?我受困于
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]
并将其映射到IO[B]
,该函数使用A
并返回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
可能解析为容器类型吗? 最佳答案
IO[A]
旨在表示返回Action
的A
,其中Action的结果可能取决于环境(含义是任何东西,变量的值,文件系统,系统时间...),并且该Action的执行也可能会修改环境。实际上, Action 的标量类型为Function0
。 Function0[A]
在调用时返回A,当然可以允许它依赖并修改环境。 IO
是另一个名称的Function0
,但它旨在将那些依赖于环境的Function0与其他依赖于环境的Function0(实际上是纯值)区分开(标记?)(如果您说f是一个总是返回相同值的function [A],值,没有任何副作用,f
和它的结果之间没有太大区别)。确切地说,标记为IO的功能必须具有副作用的并不多。就是那些没有这样标记的人必须没有。但是请注意,将不纯函数包装在IO
中是完全自愿的,但是,当您获得Function0
是纯净的时,您将无法保证。 在scala 中,使用IO
当然不是主导风格。
完全正确,但是“容器”可能意味着很多东西。由纯净返回的那一个必须尽可能轻,它必须是没有区别的。列表的要点是它们可以具有任意数量的值。由pure返回的一个必须有一个。 IO的要点在于它取决于并影响环境。纯属返回的人绝对不能做这种事情。所以实际上是包装在Function0
中的纯() => a
IO
。
并非如此,bind与flatMap相同。在编写时,map将接收从Int
到String
的函数,但是在这里,您具有从Int
到List[String]
的函数。
现在,暂时忘记IO,并考虑bind/flatMap对于Action(即Function0
)意味着什么。
让我们
val askUserForLineNumber: () => Int = {...}
val readingLineAt: Int => Function0[String] = {i: Int => () => ...}
现在,如果我们必须像bind/flatMap一样组合这些项目,以获得返回String的 Action ,则必须清楚:向读者询问行号,读取该行并返回它。那会是
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
必须是什么Function0
,pure
也很清楚。我们可以做的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(),
,我们还必须做a.unsafePerformIO
。并定义一个,而不是() => 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
所做的操作。从IO
到Function0
(带有() => io.unsafePerformIo
)以及从Function0
到IO
(带有IO { action() }
)很容易。如果您有f:A => IO [B],则也可以将其更改为f: A => Function0[B]
,只需将IO
组成为Function0
转换,即(x: A) => f(x).unsafePerformIO
即可。IO绑定(bind)中发生的情况是:
() => a.unsafePerformIO
:将a
转换为Function0
(x:A) => () => f(x).unsafePerformIO)
:将f
转换为A => Function0[B]
Function0
的默认monad,与ActionMonad
相同bind(...)
:将bind
monad的Function0
应用于刚刚转换为Function0 a
和f
IO{...}
:将结果转换回IO
。 (不确定我是否很喜欢)