Skip to content

Instantly share code, notes, and snippets.

@ubourdon
Created August 24, 2016 08:44
Show Gist options
  • Select an option

  • Save ubourdon/b9c24bdb25badac1c8363c7a3d27da12 to your computer and use it in GitHub Desktop.

Select an option

Save ubourdon/b9c24bdb25badac1c8363c7a3d27da12 to your computer and use it in GitHub Desktop.
feed a scalaz Process with recursive async function
/**
My problem is:
I have a function getData[A](params ...): Task[List[A]] that return asynchronously data
But this data is big so I cut the call in several step playing with params
In term of API, I want expose a function stream[A]: Process[Task, A]
I see Process.repeatEval & stream.async.unboundedQueue API from scalaz
but I don't understand how to use it to produce this result
*/
object Feed[A] {
// return a piece of needed data
private def getData(start: Int, chunk: Int): Task[List[A]] = ???
// the public API I want to expose
def stream: Process[Task, A]
}
@gourlaysama

Copy link
Copy Markdown

Two things:

  • No need for queues for something like this (although it would work fine with it, let's not complicate things too much)
  • You are doing your recursion at the Task level, that is, within a single Task! Remember, a Task is like a Future, it only returns a value once, at the end of whatever computation it was doing, hence why you have nothing actually streaming.

This should explain better:

import scalaz._
import scalaz.concurrent.Task
import scalaz.stream.Process
import scalaz.stream.async
import scalaz.stream.async.mutable.Queue

trait Feed[A] {

  protected def getData(start: Int, chunk: Int): Task[List[A]]

  protected def isEnd(i: Int): Boolean


  // we do the recursion at the Process level instead, halting when necessary
  private def getChunks(start: Int, chunk: Int): Process[Task, List[A]] =
    if (isEnd(start)) Process.halt  
    else Process.eval(getData(start, start + chunk)) ++ getChunks(start + chunk, chunk)

  // let's flatten the feed
  def stream: Process[Task, A] = getChunks(0, 250).flatMap(Process.emitAll)
}

object TestFeed extends Feed[Int] {
  protected def getData(start: Int, chunk: Int): Task[List[Int]] = Task { 
    println("debug: getting data!")
    Range(start,chunk).toList 
  }

  protected def isEnd(i: Int) = i >= 1000
}

object Run extends App {
  // this will output the integer 0 to 999 with "getting data!" interleaved in between
  // instead of block of it at the beginning
  TestFeed.stream.map(i => print(s"$i;")).run.run
}

@ubourdon

Copy link
Copy Markdown
Author

Si getData ne renvoi plus que une liste de E mais un résultat plus complexe qui contient notamment la condition de sorti de la récursion.

J'ai tenté ça. J'ai bon ?

trait Feed[A] {
  case class ComplexResult[A](result: List[A], nextChunk: Int, endOfData: Boolean)

  protected def getData(start: Int, chunk: Int): Task[ComplexResult[A]]

  protected def isEnd(i: Int): Boolean

  // we do the recursion at the Process level instead, halting when necessary
  private def getChunks(start: Int, chunk: Int, end: Boolean): Process[Task, A] =
    if (end) Process.halt  
    else {  
      val chunk: Task[ReadStreamEventsCompleted] = processChunk(streamUid, nextEventNumber, chunkSize)

      chunk.map { result =>
        Process.emitAll(readEvent(result)) ++ getChunks(streamUid, result.nextChunk, chunkSize, result.endOfData)
      } |> flattenTaskProcess 
    }

  private def flattenTaskProcess[A](f: Task[Process[Task, A]]): Process[Task, A] = Process eval(f) flatMap(x => x)

  def stream: Process[Task, A] = getChunks(0, 250, false)
}

@ubourdon

Copy link
Copy Markdown
Author

When the end of recursion is decide by getData result :

private def getChunks(start: Int, chunk: Int): Process[Task, ComplexResult] =
    Process.await(getData(start, start + chunk))({ c: ComplexResult =>
      val next = if (c.endOfData) Process.halt else getChunks(start + chunk, chunk)
      Process.emit(c) ++ next
    })

@ubourdon

Copy link
Copy Markdown
Author
private def getChunks(start: Int, chunk: Int): Process[Task, ComplexResult] =
    Process.await(getData(start, start + chunk))({ c: ComplexResult =>
        val next = if (c.endOfData) Process.halt else getChunks(start + chunk, chunk)
        Process.emit(c) ++ next
    })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment