viernes, febrero 15, 2013

Functional pagination (or CPS iterator pattern)

I know that I promised to write about Javascript... Not yet, but I'm almost there.

I’ve been thinking about implementing a purely functional pagination scheme for a product picker I’m working on… The motivation is that writing asynchronous code that manipulates mutable state is not to my taste.

Instead of having around the current offset counter, I’ve thought that each callback can receive (besides the data) two extra functions: one that returns the previous page and another that returns the next page (if any).

The widget is written in Javascript, which is great because you don’t have declare recursive function signatures, but you still need to use them without making any mistakes. To be sure of what I’m going to implement is not nonsense, I wrote a toy example in Scala before diving in:

import scala.collection.immutable.Range

object sandbox {

  trait PaginatedCallback[T] {
    def apply(results: List[T],
      prev: Option[PaginatedCallback[T] => Unit],
      next: Option[PaginatedCallback[T] => Unit])
  }

  def feed(callback: PaginatedCallback[Int]) = {
    def page(p: Int)(cb: PaginatedCallback[Int]): Unit = {
      val prev = if (p > 0) Some(page(p - 1)_) else None;
      val next = if (p < 10) Some(page(p + 1)_) else None;
      val results = Range(p * 10, (p + 1) * 10) toList;
      cb(results, prev, next)
    }
    page(0)(callback)
  }

  object printAll extends PaginatedCallback[Int] {
    def apply(results: List[Int],
          prev: Option[PaginatedCallback[Int] => Unit],
          next: Option[PaginatedCallback[Int] => Unit]) = {
      
      println(results);
      next match {
       case Some(cb) => cb(printAll)
       case None => println("finished")
      }
    }
  }
  
  feed(printAll)
  //> List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
  //| List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
  //| List(20, 21, 22, 23, 24, 25, 26, 27, 28, 29)
  //| List(30, 31, 32, 33, 34, 35, 36, 37, 38, 39)
  //| List(40, 41, 42, 43, 44, 45, 46, 47, 48, 49)
  //| List(50, 51, 52, 53, 54, 55, 56, 57, 58, 59)
  //| List(60, 61, 62, 63, 64, 65, 66, 67, 68, 69)
  //| List(70, 71, 72, 73, 74, 75, 76, 77, 78, 79)
  //| List(80, 81, 82, 83, 84, 85, 86, 87, 88, 89)
  //| List(90, 91, 92, 93, 94, 95, 96, 97, 98, 99)
  //| List(100, 101, 102, 103, 104, 105, 106, 107, 108, 109)
  //| finished
}

I think it’s quite neat.

It took me a while to think, but once I wrote it, it just worked. I’m sure I wouldn’t have got it right in Javascript without a little trial and error.