Scala 以非阻塞和懒惰的方式构建一个具有未来的懒散列表

Scala 以非阻塞和懒惰的方式构建一个具有未来的懒散列表,scala,nonblocking,Scala,Nonblocking,我正在为Clojure频道构建一个Scala facade,该频道提供大量数据,我希望将其表示为一个LazyList[Future[Orther[String,Int]]],左侧可以保存错误消息和右侧数据。从通道中检索每个块是一个阻塞操作,因此我希望在将来封装每个块 每个区块结果类型决定了我们应该如何继续构建惰性列表: null:通道上没有更多结果,返回列表 字符串:添加左侧(错误)并返回列表 Int:addRight(data)并为下一个块递归 我的问题是,我们是否可以以一种懒惰和非阻塞的方式

我正在为Clojure频道构建一个Scala facade,该频道提供大量数据,我希望将其表示为一个
LazyList[Future[Orther[String,Int]]]
,左侧可以保存错误消息和右侧数据。从通道中检索每个块是一个阻塞操作,因此我希望在将来封装每个块

每个区块结果类型决定了我们应该如何继续构建惰性列表:

  • null:通道上没有更多结果,返回列表
  • 字符串:添加
    左侧(错误)
    并返回列表
  • Int:add
    Right(data)
    并为下一个块递归
  • 我的问题是,我们是否可以以一种懒惰和非阻塞的方式构建这样的列表

    这是我到目前为止所想到的,但是头部是经过评估的(不是懒惰的),wait.result块:

    //Clojure“通道”虚拟
    案例类频道(vs:Any*){
    private val it=vs.toIterable.iterator
    //相当于'LazyList.empty[Future[or[String,Int]]]]//没有更多值
    大小写错误:String=>Future(左(错误))#:::LazyList.empty[Future[other[String,Int]]
    案例数据:Int=>Future(右(数据))#::lazyList(频道)
    }
    等待结果(ll,Duration.Inf)
    }
    val ll=lazyList(通道(0,1,“错误”))
    //正在检索值:0
    ll(0)
    //(没有输出,因为值0已被计算和记忆)
    法律专业(1)
    //检索值:1
    法律专业(2)
    //检索值:错误
    
    我想看到的是:

    val ll2=lazyList2(通道(0,1,“错误”))
    //(不计算)
    ll2(0)
    //正在检索值:0
    法律责任2(1)
    //检索值:1
    ll2(2)
    //检索值:错误
    
    如果您使用的是fs2,您可以从该频道构建流。给定函数

    def nextChunk: Future[A] = ???
    
    您可以使用

    val myStream: Stream[IO, A] = Stream.eval(IO.fromFuture(IO(nextChunk))).repeat
    
    在您的特定示例中,您的
    A
    Any
    ,您知道在运行时是
    Int
    String
    null
    。您可以首先将它提升到一个
    选项[String,Int]]

    def typedChunk(channel: Channel): IO[Option[Either[String, Int]]] = 
      IO.fromFuture(IO(channel.nextChunk)).map {
        case null      => None
        case s: String => Some(Left(s))
        case i: Int    => Some(Right(i))
      }
    
    def myTerminatedStream(channel: Channel): Stream[IO, Either[String, Int]] = 
      Stream.eval(typedChunk(channel)).repeat.unNoneTerminate
    
    然后您可以构建流,终止于
    None
    with

    def typedChunk(channel: Channel): IO[Option[Either[String, Int]]] = 
      IO.fromFuture(IO(channel.nextChunk)).map {
        case null      => None
        case s: String => Some(Left(s))
        case i: Int    => Some(Right(i))
      }
    
    def myTerminatedStream(channel: Channel): Stream[IO, Either[String, Int]] = 
      Stream.eval(typedChunk(channel)).repeat.unNoneTerminate
    
    这就完成了所有保持引用透明性的艰苦工作,并确保它具有正确的评估语义


    您请求的LazyList语义将很棘手:只有在Future完成评估后,您才会知道您的区块是空的,因此您需要评估Future以了解列表是否为空。LazyList能够做到这一点,但只能通过阻塞操作实现,而不是从未来实现。

    您是否考虑过fs2是一种流媒体解决方案,而不是构建自己的解决方案?它经过精心设计,能够有效、正确地解决由类似未来的事物生成的数据流的问题。你不能让一个
    LazyList[a]
    具有由未来生成的值且不阻塞。当您想要生成下一个A并且有一个未来生成A时,您必须阻塞以等待它。您的阻塞实现将按照您希望在scala 2.13.4中看到的那样工作,它修复了对
    #::
    的急切评估,谢谢,@Martijn。都是懒洋洋地建造的,太棒了。需要进一步探索fs2,看看我将如何将其用于我的外观。当我有了一个例子并运行起来供将来参考时,我会回来的。