使用箭头:如何将 (X)->IO<Y> 类型的转换应用于 Sequence<X> 类型的数据以获得 IO<Sequence<Y>>?

With Arrow: How do I apply a transformation of type (X)->IO<Y> to data of type Sequence<X> to get IO<Sequence<Y>>?

我正在使用 Arrow.kt 学习函数式编程,打算遍历路径层次结构并对每个文件进行哈希处理(并做一些其他事情)。强迫自己尽可能多地使用函数式概念。

假设我已经在代码中定义了一个 data class CustomHash(...)。下面会提到。

首先,我需要通过遍历路径来构建一系列文件。这是一个 impure/effectful 函数,所以它应该用 IO monad:

标记为这样
fun getFiles(rootPath: File): IO<Sequence<File>> = IO {
   rootPath.walk() // This function is of type (File)->Sequence<File>
}

我需要阅读文件。同样,不纯,因此用 IO

标记
fun getRelevantFileContent(file: File): IO<Array<Byte>> {
   // Assume some code here to extract only certain data relevant for my hash
}

然后我有一个计算散列的函数。如果它需要一个字节数组,那么它就是完全纯粹的。使它 suspend 因为它执行起来会很慢:

suspend fun computeHash(data: Array<Byte>): CustomHash {
   // code to compute the hash
}

我的问题是如何以功能性方式将它们链接在一起。

fun main(rootPath: File) {
   val x = getFiles(rootPath) // IO<Sequence<File>>
      .map { seq -> // seq is of type Sequence<File>
         seq.map { getRelevantFileContent(it) } // This produces Sequence<IO<Hash>>
      }
   }
}

现在,如果我尝试这样做,xIO<Sequence<IO<Hash>>> 类型。我很清楚为什么会这样。

有什么方法可以把 Sequence<IO<Any>> 变成 IO<Sequence<Any>> 吗?我想这基本上是,可能使术语不精确,采用在它们自己的协程中执行的代码块和 运行 代码块全部在同一个协程上?

如果没有 Sequence,我知道 IO<IO<Hash>> 可以通过在其中使用 flatMapIO<Hash>,但是 Sequence 当然没有IO 能力的扁平化。

Arrow 的文档有很多 "TODO" 部分,并且可以非常快速地跳转到假定大量 intermediate/advanced 函数式编程知识的文档。它对这个问题并没有真正的帮助。

首先您需要将 Sequence 转换为 SequenceK 然后您可以使用 sequence 函数来完成。


import arrow.fx.*
import arrow.core.*
import arrow.fx.extensions.io.applicative.applicative

val sequenceOfIOs: Sequence<IO<Any>> = TODO()

val ioOfSequence: IO<Sequence<Any>> = sequenceOfIOs.k()
    .sequence(IO.applicative())
    .fix()