帮助我理解这个Scala代码:scalaz IO Monad和implicits
这是对
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)() } } } 这个代码是这样使用的(我假设导入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)) 我现在正试图理解隐式的val IOMonad定义.这是我理解它的方式.这是一个scalaz.Monad,所以它需要定义scalaz.Monad特征的纯和绑定抽象值. pure接受一个值并将其转换为“容器”类型中包含的值.例如,它可以采用Int并返回List [Int].这看起来很简单. bind采用“容器”类型和将容器保存的类型映射到另一种类型的函数.返回的值是相同的容器类型,但它现在持有一个新类型.一个例子是使用List [Int]并使用将Ints映射到字符串的函数将其映射到List [String].绑定几乎和地图一样吗? bind的实现是我被困的地方.这是代码: def bind[A,f: A => IO[B]): IO[B] = IO { implicitly[Monad[Function0]].bind(() => a.unsafePerformIO,(x:A) => () => f(x).unsafePerformIO)() } 该定义采用IO [A]并使用带A的函数将其映射到IO [B]并返回IO [B].我想这样做,它必须使用flatMap来“压扁”结果(正确吗?). = IO {…}与…相同 = new IO[A] { def unsafePerformIO = implicitly[Monad[Function0]].bind(() => a.unsafePerformIO,(x:A) => () => f(x).unsafePerformIO)() } } 我认为? 隐式方法查找实现Monad [Function0]的隐式值(value,right?).这个隐含定义来自哪里?我猜这是来自隐含的val IOMonad = new Monad [IO] {…}定义,但是我们现在正处于这个定义中,事情变得有点循环,我的大脑开始陷入无限循环:) 此外,bind(()=> a.unsafePerformIO)的第一个参数似乎是一个不带参数的函数,并返回a.unsafePerformIO.我该怎么看? bind将容器类型作为其第一个参数,所以maybe()=> a.unsafePerformIO解析为容器类型? 解决方法
IO [A]旨在表示返回A的Action,其中Action的结果可能取决于环境(意味着任何内容,变量值,文件系统,系统时间……),并且操作的执行也可能修改环境.实际上,Action的scala类型是Function0. Function0 [A]在被调用时返回A,当然可以依赖并修改环境. IO是另一个名称下的Function0,但是它用于区分(标记?)那些依赖于环境的Function0和其他的,它们实际上是纯值(如果你说f是一个函数[A],它总是返回相同的值,没有任何副作用,f与其结果之间没有太大差异).确切地说,标记为IO的函数必须具有副作用.那些没有标记的人必须没有.但是请注意,在IO中包含不纯函数完全是自愿的,当你得到一个纯粹的Function0时,你无法保证.使用IO肯定不是scala中的主导风格.
很对,但“容器”可能意味着很多东西.纯粹归来的那个必须尽可能轻,它必须是没有区别的.列表的重点是它们可能具有任意数量的值.纯粹归来的人必须有一个. IO的重点在于它依赖并影响环境.纯粹归来的人必须不做这样的事情.所以它实际上是纯函数0()=> a,包裹在IO中.
不是这样,bind与flatMap相同.在你写的时候,map会接收一个从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,f: A => Function0[B]) = () => {f(a())()} 所以我们知道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.unsafePerformIO,而不仅仅是执行a().并定义一个,而不是()=>身体,我们写IO {body} object IOMonad extends Monad[IO] { def pure[A](a: => A) = IO {a} def bind[A,f: A => IO[B]) = IO {f(a.unsafePerformIO).unsafePerformIO} } 在我看来,这将是足够好的.但事实上它重复了ActionMonad.您引用的代码中的要点是避免这种情况,而是重用对Function0所做的操作.一个很容易从IO到Function0(带()=> io.unsafePerformIo)以及从Function0到IO(带IO {action()}).如果你有f:A => IO [B],您也可以将其更改为f:A => Function0 [B],只需通过组合IO到Function0变换,所以(x:A)=> F(X).unsafePerformIO. IO的绑定在这里发生的是: >()=> a.unsafePerformIO:将a转换为Function0 (不确定我喜欢它) (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |