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

thr*_*ups 5 monads scala scalaz

这是这个问题的后续内容.

这是我试图理解的代码(来自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)()
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这段代码是这样使用的(我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))
Run Code Online (Sandbox Code Playgroud)

我现在正试图理解这个implicit val IOMonad定义.这是我理解它的方式.这是一个scalaz.Monad,因此需要定义purebind抽象scalaz.Monad特征的值.

pure获取一个值并将其转换为"容器"类型中包含的值.例如,它可以采取Int并返回a List[Int].这看起来很简单.

bind采用"容器"类型和将容器保存的类型映射到另一种类型的函数.返回的值是相同的容器类型,但它现在持有一个新类型.一个例子是使用将s 映射到s 的函数将其List[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)()
}
Run Code Online (Sandbox Code Playgroud)

该定义采用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)()
  }
}
Run Code Online (Sandbox Code Playgroud)

我认为?

implicitly方法查找实现的隐式值(value,right?)Monad[Function0].这个隐含定义来自哪里?我猜这是来自implicit val IOMonad = new Monad[IO] {...}定义,但我们现在正处于这个定义中,事情变得有点循环,我的大脑开始陷入无限循环:)

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

Did*_*ont 14

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

pure接受一个值并将其转换为"容器"类型中包含的值.

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

绑定几乎与地图相同

不是这样,bind与flatMap相同.当你写,地图会从接收功能IntString,但在这里,你具备的功能Int,以List[String]

现在,忘记IO片刻,考虑一下bind/flatMap对Action的意义,也就是说Function0.让我们

val askUserForLineNumber: () => Int = {...}
val readingLineAt: Int => Function0[String] = {i: Int  => () => ...}
Run Code Online (Sandbox Code Playgroud)

现在,如果我们必须像bind/flatMap那样组合这些项来获取返回String的动作,那么它必须非常清楚:向读者询问行号,读取该行并返回它.那就是

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

更一般地说

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

而且更短:

def bind[A,B](a: Function0[A], f: A => Function0[B]) 
  = () => {f(a())()}
Run Code Online (Sandbox Code Playgroud)

所以我们知道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())()
}
Run Code Online (Sandbox Code Playgroud)

现在,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}
}
Run Code Online (Sandbox Code Playgroud)

在我看来,这将是足够好的.但事实上它重复了ActionMonad.您引用的代码中的要点是避免这种情况并重新使用所做的操作Function0.从一个容易去IOFunction0(有() => io.unsafePerformIo)以及来自Function0IO(与IO { action() }).如果您有f:A => IO [B],您也可以将其更改为f: A => Function0[B],只需通过组合IOto Function0变换,即可(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(...):应用bind的的Function0单子的论据af刚才已经转换为Function0
  5. 封闭IO{...}:将结果转换回IO.

(不确定我喜欢它)