This page is no longer maintained — Please continue to the home page at www.scala-lang.org

zipper

2 replies
rytz
Joined: 2008-07-01,
User offline. Last seen 45 weeks 5 days ago.
Hi,
inspired by Greg Meredith's blog post and Jesper Nordenberg's comment i implemented some code generation which allows navigating trees of caseclasses and performing functional updates on them.
Here's an example. Suppose we model the state of a pacman game the following way:
scala> @zip case class Pacman(lives: Int = 3, superMode: Boolean = false)
scala> @zip case class Game(state: Symbol = 'pause, pacman: Pacman = Pacman())
scala> val g = Game() g: Game = Game('pause,Pacman(3,false))


Changing the game state to 'run is simple using the copy method:
scala> val g1 = g.copy(state = 'run) g1: Game = Game('run,Pacman(3,false))


However, changing pacman's super mode is much more cumbersome (and it gets worse for deeper structures):
scala> val g2 = g1.copy(pacman = g1.pacman.copy(superMode = true)) g2: Game = Game('run,Pacman(3,true))


Using the compiler-generated location classes this gets much easier:
scala> val g3 = g1.loc.pacman.superMode set true g3: Game = Game('run,Pacman(3,true))



What happens? For a case class "@zip case class C(x: A, y: B)", suppose T is another case class annotated with @zip, U is any other type, the compilergenerates:
class CLoc[T, P <: Loc[T, _, _]](v: C, f: C => Option[P]) extends Loc[T, C, P](v, f) { self =>  override def copy(v: C) = new CLoc[T, P](v, f)   def x: ALoc[T, CLoc[T, P]] = new ALoc[T, CLoc[T, P]](v.x, (x: A) => {    Some(self.copy(v.copy(x = x)))   })  def y: Loc[T, B, CLoc[T, P]] = new Loc[T, B, CLoc[T, P]](v.y, (y: B) => {     Some(self.copy(v.copy(y = y)))  }) }

in the class C
def loc = new CLoc[C, Nothing](this, v => None)


The class Loc is defined in the library as
/**  * T: top type * E: element type of this location  * P: parent location type */ class Loc[T, E, P <: Loc[T, _, _]](v: E, f: E => Option[P]) {  def up: Option[P] = f(v)   def top: T = up match {    case Some(p) => p.top     case _ => v.asInstanceOf[T]  }   def copy(v: E): Loc[T, E, P] = new Loc[T, E, P](v, f)  def set(v: E) = copy(v).top }



The prototype is on github, many things are not worked out yet e.g. generic case classes, case classes without copy method or name clashes.
Comments welcome :)Lukas
Jesper Nordenberg
Joined: 2008-12-27,
User offline. Last seen 42 years 45 weeks ago.
Re: zipper
Pretty nifty. I've done some experimenting myself with a library-only implementation. The syntax is not as nice as with a compiler modification of course. This is the nicest use site syntax I've found so far (the record declaration syntax can be improved quite a bit so I prefer not to show it now :) ):

scala> val a = A(1, "Hello")
a: records.A = A(1,Hello)

scala> val b = B(a, a)
b: records.B = B(A(1,Hello),A(1,Hello))

scala> val b2 = b ba1 (_ a1) set 4
b2: records.B = B(A(4,Hello),A(1,Hello))

scala> val p = b2 ba2 (_ a2)
p: records.FieldPath[records.B,String] = records$ConsPath@973678

scala> p set "Bye"
res0: records.B = B(A(4,Hello),A(1,Bye))

/Jesper Nordenberg

--- On Wed, 6/9/10, Lukas Rytz <lukas [dot] rytz [at] epfl [dot] ch> wrote:
inspired by Greg Meredith's blog post and Jesper Nordenberg's comment i implemented some code generation which allows navigating trees of caseclasses and performing functional updates on them.
Here's an example. Suppose we model the state of a pacman game the following way:
scala> @zip case class Pacman(lives: Int = 3, superMode: Boolean = false)
scala> @zip case class Game(state: Symbol = 'pause, pacman: Pacman = Pacman())
scala> val g = Game() g: Game = Game('pause,Pacman(3,false))


Changing the game state to 'run is simple using the copy method:
scala> val g1 = g.copy(state = 'run) g1: Game = Game('run,Pacman(3,false))


However, changing pacman's super mode is much more cumbersome (and it gets worse for deeper structures):
scala> val g2 = g1.copy(pacman = g1.pacman.copy(superMode = true)) g2: Game = Game('run,Pacman(3,true))


Using the compiler-generated location classes this gets much easier:
scala> val g3 = g1.loc.pacman.superMode set true g3: Game = Game('run,Pacman(3,true))



What happens? For a case class "@zip case class C(x: A, y: B)", suppose T is another case class annotated with @zip, U is any other type, the compilergenerates:
class CLoc[T, P <: Loc[T, _, _]](v: C, f: C => Option[P]) extends Loc[T, C, P](v, f) { self =>  override def copy(v: C) = new CLoc[T, P](v, f)   def x: ALoc[T, CLoc[T, P]] = new ALoc[T, CLoc[T, P]](v.x, (x: A) => {    Some(self.copy(v.copy(x = x)))   })  def y: Loc[T, B, CLoc[T, P]] = new Loc[T, B, CLoc[T, P]](v.y, (y: B) => {     Some(self.copy(v.copy(y = y)))  }) }

in the class C
def loc = new CLoc[C, Nothing](this, v => None)


The class Loc is defined in the library as
/**  * T: top type * E: element type of this location  * P: parent location type */ class Loc[T, E, P <: Loc[T, _, _]](v: E, f: E => Option[P]) {  def up: Option[P] = f(v)   def top: T = up match {    case Some(p) => p.top     case _ => v.asInstanceOf[T]  }   def copy(v: E): Loc[T, E, P] = new Loc[T, E, P](v, f)  def set(v: E) = copy(v).top }



The prototype is on github, many things are not worked out yet e.g. generic case classes, case classes without copy method or name clashes.
Comments welcome :)Lukas

Alex Cruise
Joined: 2008-12-17,
User offline. Last seen 2 years 26 weeks ago.
Re: zipper
I coughed up some assless (i.e. not even half-assed) thoughts on this topic 2.5 years ago, and there was a modicum of discussion on it at the time:

http://wiki.liftweb.net/index.php/ASRUSP_is_A_Scala_Record_Update_Syntax_Proposal

-0xe1a

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland