Skip to content

Instantly share code, notes, and snippets.

@wrwills
Created November 30, 2010 13:47
Show Gist options
  • Save wrwills/721698 to your computer and use it in GitHub Desktop.
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
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))
}
@retronym
Copy link

I really need to add Lenses to my answer for the StackOverflow question: http://stackoverflow.com/questions/3900307/cleaner-way-to-update-nested-structures

@wrwills
Copy link
Author

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