Created
November 30, 2010 13:47
-
-
Save wrwills/721698 to your computer and use it in GitHub Desktop.
playing with some of the ideas from the Haskell fclabels library using the new Lens functionality in scalaz
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scalaz._ | |
/** | |
* playing with some of the ideas from the Haskell fclabels library | |
* using the new Lens functionality in scalaz | |
* | |
* http://hackage.haskell.org/package/fclabels | |
*/ | |
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.city, (x:Place, y:String) => x.copy(city = y)) | |
val placeLens: Lens[Person,Place] = Lens((x:Person) => x.place, (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 = p.place.copy(city = 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)] = | |
Lens( | |
(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)] = | |
Lens( | |
(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 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
I really need to add Lenses to my answer for the StackOverflow question: http://stackoverflow.com/questions/3900307/cleaner-way-to-update-nested-structures