加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

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

发布时间:2020-12-16 18:44:44 所属栏目:安全 来源:网络整理
导读:这是对 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):
这是对 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中的主导风格.

pure takes a value and turns it into a value contained in the
“container” type.

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

bind pretty much the same as map

不是这样,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
>(x:A)=> ()=> f(x).unsafePerformIO):将f变为A => Function0 [B]
>隐式[Monad [Function0]]:得到Function0的默认monad,与上面的ActionMonad一样
> bind(…):将Function0 monad的绑定应用于刚刚转换为Function0的参数a和f
>封闭的IO {…}:将结果转换回IO.

(不确定我喜欢它)

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读