设计参考透明功能,从通道读取

时间:2018-06-04 16:11:02

标签: scala io functional-programming scalaz scala-cats

我正在尝试坚持纯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并不重要),但我看不出它是如何帮助的(我们需要mock SeekableByteChannel 1}},但现在已包含在IO)中。

你能帮助我以纯FP风格设计这个功能(或者至少让它不那么丑陋)?

1 个答案:

答案 0 :(得分:4)

When you need to wrap impure code, most of the time (based on my experience), it's not going to "be pretty". But, what we gain is that we only have a single point which deals with the "messy stuff", and we get a nice abstraction from there on.

What we want is to create a stream which is bound by an IO effect. Instead of Stream[Id, SeekableByteChannel], we're really in Stream[IO, SeekableByteChannel] because we are in the IO effect context:

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)
        }
      }
    }
}

This way, we suspend the side effect, which is what we want to make these side effectful computation RT, and apply transformations on the stream from this point on.