Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
playing with some of the ideas from the Haskell fclabels library using the new Lens functionality in scalaz
import scalaz._
* playing with some of the ideas from the Haskell fclabels library
* using the new Lens functionality in scalaz
object LensTest {
case class Person(name: String, age: Int, place: Place)
case class Place(city: String, country: String)
val ageLens: Lens[Person,Int] = Lens((x:Person) => x.age, (x:Person, y:Int) => x.copy(age = y))
val jan = Person("Jan", 71, Place("Utrecht", "Netherlands"))
ageLens.get(jan) // 71
ageLens.set(jan,74) // Person(Jan,74,Place(Utrecht,Netherlands))
// but you could just do:
jan.copy(age = 74)
// let's try to make it a little more complicated...
// in the fclabels haskell library these lenses are derived using template haskell
// perhaps something similar could be accomplished in scala using a compiler plugin or reflection?
val cityLens: Lens[Place,String] = Lens((x:Place) =>, (x:Place, y:String) => x.copy(city = y))
val placeLens: Lens[Person,Place] = Lens((x:Person) =>, (x:Person, y:Place) => x.copy(place = y))
//moveToAmsterdam :: Person -> Person
//moveToAmsterdam = setL (city . place) "Amsterdam"
def moveCity: Lens[Person,String] = placeLens andThen cityLens
moveCity.set(jan, "Amsterdam") // Person(Jan,71,Place(Amsterdam,Netherlands))
// this is perhaps slihtly cleaner than the traditional
def setCity(p: Person, c: String) = p.copy(place = = c) )
//def moveToAmsterdam(p: Person): Person =
* now what if we want to define a lens to do 2 things. eg change the age and the city?
* the type signature will need to be Lens[Person, (Int,String)]
* this would be provided for free by &&& but &&& fails retention so we need
* to define it ourselves
def ageAndCityLens1: Lens[Person, (Int,String)] =
(x:Person) => (ageLens.get(x),moveCity.get(x)),
(x:Person, y: (Int,String)) => ageLens.set(moveCity.set(x,y._2), y._1) )
// todo: figure out why &&& would fail retention
def &&&[A,B,C](a: Lens[A,B], b: Lens[A,C]): Lens[A,(B,C)] =
(x:A) => (a.get(x), b.get(x)),
(x:A, y: (B,C)) => a.set( b.set(x,y._2), y._1) )
def ageAndCityLens: Lens[Person, (Int,String)] = &&&(ageLens,moveCity)
ageAndCityLens.set(jan, (74, "Amsterdam") ) // Person(Jan,74,Place(Amsterdam,Netherlands))
// moveToAmsterdamOverTwoYears :: Person -> Person
// moveToAmsterdamOverTwoYears = modL ageAndCity (\(a, b) -> (a+2, "Amsterdam"))
val moveToAmsterdamOverTwoYears1: State[Person,(Int,String)] =
ageAndCityLens map ((x:(Int,String)) => (x._1 + 2, "Amsterdam") )
moveToAmsterdamOverTwoYears1 ! jan // (73,Amsterdam)
// nearly there but we want the Person -- not the Int and String
ageAndCityLens.set(jan, moveToAmsterdamOverTwoYears1 ! jan)
// but we can do better:
val moveToAmsterdamOverTwoYears: State[Person,(Int,String)] =
ageAndCityLens.mods( (x:(Int,String)) => (x._1 + 2, "Amsterdam") )
moveToAmsterdamOverTwoYears ~> jan // Person(Jan,71,Place(Utrecht,Netherlands))

I really need to add Lenses to my answer for the StackOverflow question:


wrwills commented Nov 30, 2010

I was thinking while working through this that lenses and zippers deal with a similar problem. Interesting to hear about Lukas Rytz' compiler extension. Maybe an '@lens' annotation could be possible too?

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