这是对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
可能会解析为容器类型吗?
答案 0 :(得分:14)
IO[A]
旨在表示Action
返回A
,其中Action的结果可能取决于环境(意味着任何事情,变量值,文件系统,系统时间) ...)并且动作的执行也可以修改环境。实际上,Action的scala类型为Function0
。 Function0[A]
在调用时返回A,当然可以依赖并修改环境。 IO
在另一个名称下是Function0
,但是它旨在区分(标记?)那些依赖于环境的Function0和其他的,这实际上是纯值(如果你说f是一个函数) [A]始终返回相同的值,没有任何副作用,f
与其结果之间没有太大差异。确切地说,标记为IO的函数必须具有副作用。那些没有标记的人必须没有。但请注意,在IO
中包含不纯函数完全是自愿的,当你得到Function0
它是纯粹的时,你无法保证。 使用IO
肯定不是scala中的主导风格。
pure取一个值并将其转换为包含在其中的值 “容器”类型。
非常正确,但“容器”可能意味着很多东西。纯粹归来的那个必须尽可能轻,它必须是没有区别的。列表的重点是它们可能具有任意数量的值。纯粹归来的人必须有一个。 IO的重点在于它依赖并影响环境。纯粹归来的人必须不做这样的事情。所以它实际上是纯Function0
() => a
,包含在IO
中。
绑定与地图
几乎相同
不是这样,bind与flatMap相同。在您编写时,地图会收到Int
到String
的函数,但是您可以在此处使用Int
到List[String]
的函数
现在,忘记IO片刻,考虑一下bind / flatMap对于一个Action的意义,即Function0
。
我们有
val askUserForLineNumber: () => Int = {...}
val readingLineAt: Int => Function0[String] = {i: Int => () => ...}
现在,如果我们必须像bind / flatMap那样组合这些项来获取返回String的动作,那么它必须非常清楚:向读者询问行号,读取该行并返回它。那将是
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 =&gt; IO [B],您也可以将其更改为f: A => Function0[B]
,只需使用IO
到Function0
转换,即(x: A) => f(x).unsafePerformIO
。
IO的绑定在这里发生的是:
() => a.unsafePerformIO
:将a
变为Function0
(x:A) => () => f(x).unsafePerformIO)
:将f
变成A =&gt; Function0[B]
Function0
的默认monad,与上面的ActionMonad
相同bind(...)
:将bind
monad的Function0
应用于刚刚转换为Function0的参数a
和f
IO{...}
:将结果转换回IO
。 (不确定我喜欢它)