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,因此需要定义pure和bind抽象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,当然可以依赖并修改环境.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 => () => ...}
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.从一个容易去IO到Function0(有() => io.unsafePerformIo)以及来自Function0于IO(与IO { action() }).如果您有f:A => IO [B],您也可以将其更改为f: A => Function0[B],只需通过组合IOto Function0变换,即可(x: A) => f(x).unsafePerformIO.
IO的绑定在这里发生的是:
() => a.unsafePerformIO:a变成一个Function0(x:A) => () => f(x).unsafePerformIO):变成fA =>Function0[B]Function0,与ActionMonad上面的相同bind(...):应用bind的的Function0单子的论据a和f刚才已经转换为Function0IO{...}:将结果转换回IO.(不确定我喜欢它)