You can use the continuations plugin. After the plugin does its translation works, it has similarities to the Cont
monad and the shift
and reset
from Oleg. The tricky part is to figure out the types. So here is my translation:
import util.continuations._
import collection.mutable.ListBuffer
sealed trait Zipper[A] { def zipUp: Seq[A] }
case class ZDone[A](val zipUp: Seq[A]) extends Zipper[A]
case class Z[A](current: A, forward: Option[A] => Zipper[A]) extends Zipper[A] {
def zipUp = forward(None).zipUp
}
object Zipper {
def apply[A](seq: Seq[A]): Zipper[A] = reset[Zipper[A], Zipper[A]] {
val coll = ListBuffer[A]()
val iter = seq.iterator
while (iter.hasNext) {
val a = iter.next()
coll += shift { (k: A=>Zipper[A]) =>
Z(a, (a1: Option[A]) => k(a1.getOrElse(a)))
}
}
ZDone(coll.toList)
}
}
The continuations plugin has support for while
loop but not for map
or flatMap
, so I have made the choice of using while
and a mutable ListBuffer
to capture the possibly updated elements. The make_zipper
function is translated into the companion Zipper.apply
- a typical Scala location for creating new objects or collection. The data type is translated into a sealed trait with two case classes extended it. And I have put the zip_up function as a method of Zipper
with different implementations for each case class. Also pretty typical.
Here it is in action:
object ZipperTest extends App {
val sample = for (v <- 1 to 5) yield (v, (1 to v).reduceLeft(_ * _))
println(sample) // Vector((1,1), (2,2), (3,6), (4,24), (5,120))
def extract134[A](seq: Seq[A]) = {
val Z(a1, k1) = Zipper(seq)
val Z(a2, k2) = k1(None)
val Z(a3, k3) = k2(None)
val Z(a4, k4) = k3(None)
List(a1, a3, a4)
}
println(extract134(sample)) // List((1,1), (3,6), (4,24))
val updated34 = {
val Z(a1, k1) = Zipper(sample)
val Z(a2, k2) = k1(None)
val Z(a3, k3) = k2(None)
val Z(a4, k4) = k3(Some(42 -> 42))
val z = k4(Some(88 -> 22))
z.zipUp
}
println(updated34) // List((1,1), (2,2), (42,42), (88,22), (5,120))
}
How did I figure the types for shift
, k
and reset
or how to translate T.mapM
?
I looked at mapM
and I know it will allow me to get a Cont
, but I am not sure what is inside the Cont
as it depends on the shift. So I start inside the shift. Ignoring the haskell return
to construct a Cont
, shift returns a Zipper
. I also guess that I need to add an element of type A
to my collection to build. So the shift will be in the "hole" where an element of type A
is expected, therefore k
will be a A=>?
function. Let's assume this. I'm putting question marks after the types I wasn't so sure about. So I started with:
shift { (k: A?=>?) =>
Z(a, ?)
}
Next I looked (hard) at (k . maybe a id)
. The function maybe a id
will return an A
, so that is consistent with what k
takes as an argument. It is the equivalent of a1.getOrElse(a)
. Also because I need to fill in Z(a, ?)
, I need to figure out how to get a function from option to Zipper. The simplest way is to assume that k
returns a Zipper
. Also, looking at how the zipper is used k1(None)
or k1(Some(a))
, I know I have to give a way for the user to optionally replace the elements, which is what the forward
function does. It continues with the original a
or an updated a
. It starts to make sense. So now I have:
shift { (k: A=>Zipper[A]) =>
Z(a, (a1: Option[A]) => k(a1.getOrElse(a)))
}
Next I look at mapM
again. I see that it is composed with return . ZDone
. Ignoring return
again (because it is just for the Cont
monad), I see that ZDone
will take the resulting collection. So that is perfect, I just need to put coll
in it and by the time the program arrives there, it will have the updated elements. Also, the type of the expression inside the reset
is now consistent with the return type of k
which is Zipper[A]
.
Finally I fill in the type for reset which the compiler can infer, but when I guess right, it gives me a (false) sense of confidence I know what is going on.
My translation is not as general or as pretty as the Haskell one because it does not preserve the type from the collection and uses mutation but hopefully it is easier to understand.
Edit: Here is the version that preserves the type and uses an immutable list so that z.zipUp == z.zipUp
:
import util.continuations._
import collection.generic.CanBuild
import collection.SeqLike
sealed trait Zipper[A, Repr] { def zipUp: Repr }
case class ZDone[A, Repr](val zipUp: Repr) extends Zipper[A, Repr]
case class Z[A, Repr](current: A,
forward: Option[A] => Zipper[A, Repr]) extends Zipper[A, Repr] {
def zipUp = forward(None).zipUp
}
object Zipper {
def apply[A, Repr](seq: SeqLike[A, Repr])
(implicit cb: CanBuild[A, Repr]): Zipper[A, Repr] = {
type ZAR = Zipper[A, Repr]
def traverse[B](s: Seq[A])(f: A => B@cps[ZAR]): List[B]@cps[ZAR] =
if (s.isEmpty) List()
else f(s.head) :: traverse(s.tail)(f)
reset {
val list = traverse(seq.toSeq)(a => shift { (k: A=>ZAR) =>
Z(a, (a1: Option[A]) => k(a1.getOrElse(a)))
})
val builder = cb() ++= list
ZDone(builder.result): ZAR
}
}
}
By the way, here are additional resources on the continuation monad in scala:
- http://blog.tmorris.net/continuation-monad-in-scala/
- what I put myself through when I first tried it out: https://gist.github.com/huynhjl/4077185; it includes translating to Scala various Haskell continuation examples as well as some example from Tiark's paper (but not using the continuation plugin which points more clearly the similarity between the approach).
- if you download scalaz, you may be able to make Tony Morris's cont a scalaz Monad instance and use scalaz traverse - then the translation to scala would then be a more literal one.