Created

Embed URL

HTTPS clone URL

SSH clone URL

You can clone with HTTPS or SSH.

Download Gist

playing with some of the ideas from the Haskell fclabels library using the new Lens functionality in scalaz

View LensTest.scala
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
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 really need to add Lenses to my answer for the StackOverflow question: http://stackoverflow.com/questions/3900307/cleaner-way-to-update-nested-structures

Owner

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
Something went wrong with that request. Please try again.