设计用于从通道读取的引用透明函数

Designing referentially transparent function for reading from channel

我试图坚持纯 FP 风格,并希望设计一个引用透明的函数。

我有一个java.nio.channels.SeekableByteChannel,这是一个数据源。打开文件并获取 SeekableFileChannel 实例后,我需要读取文件的第一行并使用这些行来确定查找位置。

所以我创建了以下函数:

object AdjustChannelAndGetStream {

  def apply(ch: SeekableFileChannel)
           (firstChunksToOffset: List[Array[Byte]] => Long): fs2.Stream[Id, Array[Byte]] {
    val offset = //depending on the first bytes read from the file 
                 //get the number of bytes read before
    val newChannel = ch.position(offset)
    //finally wrap newChannel into fs2.Stream
  }
}

问题是,函数看起来很丑。它没有暂停副作用,这使得测试变得困难(模拟SeekableByteChannel)。

我倾向于将 SeekableByteChannel 包装成 IO[SeekableByteChannel](Scalaz/Cats 无所谓),但我看不出它有什么帮助(我们需要相同的 mockSeekableByteChannel,但现在包含在 IO) 中。

你能帮我用纯 FP 风格设计这个函数吗(或者至少让它不那么难看)?

当你需要包装不纯的代码时,大多数时候(根据我的经验),它不会 "be pretty"。但是,我们得到的是我们只有一个处理 "messy stuff" 的点,我们从那里得到了一个很好的抽象。

我们想要的是创建一个由 IO 效果绑定的流。我们实际上处于 Stream[IO, SeekableByteChannel] 而不是 Stream[Id, SeekableByteChannel],因为我们处于 IO 效果上下文中:

import java.nio.channels.SeekableByteChannel
import cats.effect.IO

object AdjustChannelAndGetStream {
    def apply(ch: SeekableByteChannel)(
        firstChunksToOffset: List[Array[Byte]] => Long)
      : fs2.Stream[IO, SeekableByteChannel] = {
      fs2.Stream.eval {
        IO {
          val offset: Int = ???
          ch.position(offset)
        }
      }
    }
}

通过这种方式,我们暂停了副作用,这就是我们想让这些副作用计算成为 RT 的原因,并从此时开始对流应用转换。