帮助我理解这个Scala代码:scalaz IO Monad和implicits

时间:2011-09-14 15:38:25

标签: scala monads scalaz

这是对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特征的bindscalaz.Monad个抽象值。

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

bind采用“容器”类型和将容器保存的类型映射到另一种类型的函数。返回的值是相同的容器类型,但它现在持有一个新类型。一个示例是使用将List[Int]映射到List[String]的函数将Int映射到Stringbindmap几乎相同吗?

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可能会解析为容器类型吗?

1 个答案:

答案 0 :(得分:14)

IO[A]旨在表示Action返回A,其中Action的结果可能取决于环境(意味着任何事情,变量值,文件系统,系统时间) ...)并且动作的执行也可以修改环境。实际上,Action的scala类型为Function0Function0[A]在调用时返回A,当然可以依赖并修改环境。 IO在另一个名称下是Function0,但是它旨在区分(标记?)那些依赖于环境的Function0和其他的,这实际上是纯值(如果你说f是一个函数) [A]始终返回相同的值,没有任何副作用,f与其结果之间没有太大差异。确切地说,标记为IO的函数必须具有副作用。那些没有标记的人必须没有。但请注意,在IO中包含不纯函数完全是自愿的,当你得到Function0它是纯粹的时,你无法保证。 使用IO肯定不是scala中的主导风格

  

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

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

  

绑定与地图

几乎相同

不是这样,bind与flatMap相同。在您编写时,地图会收到IntString的函数,但是您可以在此处使用IntList[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必须是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所做的操作。从IOFunction0(使用() => io.unsafePerformIo)以及从Function0IO(使用IO { action() })可轻松实现。如果你有f:A =&gt; IO [B],您也可以将其更改为f: A => Function0[B],只需使用IOFunction0转换,即(x: A) => f(x).unsafePerformIO

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

  1. () => a.unsafePerformIO:将a变为Function0
  2. (x:A) => () => f(x).unsafePerformIO):将f变成A =&gt; Function0[B]
  3. 隐式[Monad [Function0]]:获取Function0的默认monad,与上面的ActionMonad相同
  4. bind(...):将bind monad的Function0应用于刚刚转换为Function0的参数af
  5. 封闭的IO{...}:将结果转换回IO
  6. (不确定我喜欢它)