5

这是对这个问题的跟进。

这是我试图理解的代码(来自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,所以它需要定义purebind抽象scalaz.Monad特征的值。

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

bind接受一个“容器”类型和一个将容器持有的类型映射到另一种类型的函数。返回的值是相同的容器类型,但它现在拥有一个新类型。一个示例是使用将s 映射到s的函数将aList[Int]映射到 a 。和差不多吗?List[String]IntStringbindmap

的实施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]的函数。我想要做到这一点,它必须用来“压平”结果(对吗?)。AIO[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解析为容器类型?

4

1 回答 1

14

IO[A]旨在表示Action返回 an A,其中 Action 的结果可能取决于环境(意味着任何东西、变量的值、文件系统、系统时间......),并且 action 的执行也可能会修改环境。实际上,Action 的 scala 类型是Function0. Function0[A]调用时返回一个 A 并且它当然可以依赖和修改环境。IOFunction0在另一个名称下,但它旨在区分(标记?)那些依赖于环境的 Function0 和其他那些实际上是纯值的函数(如果你说 f 是一个总是返回相同值的函数 [A],没有任何副作用,两者之间没有太大区别f及其结果)。准确地说,标记为 IO 的函数不一定有副作用。就是那些没有被贴上标签的人一定没有。但是请注意,包装不纯函数IO完全是自愿的,当您获得 a 时,您无法保证Function0它是纯函数。usingIO肯定不是 scala 的主要风格

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

非常正确,但是“容器”可能意味着很多东西。而pure归来的一定要尽可能的轻,一定是没有区别的。列表的要点是它们可能有任意数量的值。pure 返回的必须有一个。IO的关键在于它依赖并影响环境。pure返回的那个人绝对不能做这种事。所以它实际上是纯粹的Function0 () => a,包裹在其中IO

bind 和 map 差不多

不是这样,bind 和 flatMap 是一样的。在你写的时候, map 会收到一个函数 from Intto String,但是这里你有函数 from InttoList[String]

现在,暂时忘记 IO 并考虑 bind/flatMap 对 Action 意味着什么,即Function0. 让我们

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

现在,如果我们必须像 bind/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(),我们必须做,而不是仅仅做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。一个人很容易从IOto Function0(with () => io.unsafePerformIo) 以及 from Function0to IO(with IO { action() })。如果您有 f: A => IO[B],您也可以将其更改为f: A => Function0[B],只需与IOtoFunction0变换组合,因此(x: A) => f(x).unsafePerformIO

在 IO 的绑定中发生的情况是:

  1. () => a.unsafePerformIO:a变成一个Function0
  2. (x:A) => () => f(x).unsafePerformIO): 变成fA =>Function0[B]
  3. 隐式[Monad[Function0]]:获取默认的monad Function0,和ActionMonad上面的很像
  4. bind(...): 将bindmonadFunction0应用于参数a并且f刚刚转换为 Function0
  5. 封闭的IO{...}: 将结果转换回IO.

(不确定我是否喜欢它)

于 2011-09-14T17:14:15.190 回答