Shapeless: generic lens, parameterized by class or field

Based:

import shapeless._ case class Content(field: Int) lens[Content] >> 'field 

I am trying to create a lens creation method, something like:

 def makeLens[T <: Product](s: Symbol) = lens[T] >> s 

But this seems unobvious. Can this be done?

If not, the end result I'm trying to achieve is a general method for updating nested Maps with the contents of the case class, for example:

 import scalaz._ import Scalaz._ import PLens._ import shapeless._ import shapeless.contrib.scalaz._ def nestedMapLens[R, T <: Product](outerKey: String, innerKey: Int, f: Symbol) = ~((lens[T] >> f).asScalaz) compose mapVPLens(innerKey) compose mapVPLens(outerKey) 

I cannot get it to work when it is parameterized by T and f. Are there other idiomatic solutions without patterns?

Thanks!

+8
scala lenses scalaz shapeless
source share
1 answer

The problem with your makeLens is that we want, for example, makeLens[Content]('foo) fail at compile time and this is not possible using the regular Symbol argument. You need additional implicit arguments to track the singleton type for the given name and to prove that it is the name of a member of the case class:

 import shapeless._, ops.record.{ Selector, Updater }, record.FieldType class MakeLens[T <: Product] { def apply[K, V, R <: HList](s: Witness.Aux[K])(implicit gen: LabelledGeneric.Aux[T, R], sel: Selector.Aux[R, K, V], upd: Updater.Aux[R, FieldType[K, V], R] ): Lens[T, V] = lens[T] >> s } def makeLens[T <: Product] = new MakeLens[T] 

And then:

 scala> case class Content(field: Int) defined class Content scala> makeLens[Content]('field) res0: shapeless.Lens[Content,Int] = shapeless.Lens$$anon$6@7d7ec2b0 

But makeLens[Content]('foo) will not compile (this is what we want).

For your nestedMapLens you need the same tracking:

 import scalaz._, Scalaz._ import shapeless.contrib.scalaz._ case class LensesFor[T <: Product]() { def nestedMapLens[K, V, R <: HList]( outerKey: String, innerKey: Int, s: Witness.Aux[K] )(implicit gen: LabelledGeneric.Aux[T, R], sel: Selector.Aux[R, K, V], upd: Updater.Aux[R, FieldType[K, V], R] ): PLens[Map[String, Map[Int, T]], V] = (lens[T] >> s).asScalaz.partial.compose( PLens.mapVPLens(innerKey) ).compose( PLens.mapVPLens(outerKey) ) } 

Please note that I accept build.sbt as follows:

 scalaVersion := "2.11.2" libraryDependencies ++= Seq( "com.chuusai" %% "shapeless" % "2.0.0", "org.typelevel" %% "shapeless-scalaz" % "0.3" ) 

Now let's define an example map and some lenses:

 val myMap = Map("foo" -> Map(1 -> Content(13))) val myFoo1Lens = LensesFor[Content].nestedMapLens("foo", 1, 'field) val myBar2Lens = LensesFor[Content].nestedMapLens("bar", 2, 'field) 

And then:

 scala> myFoo1Lens.get(myMap) res4: Option[Int] = Some(13) scala> myBar2Lens.get(myMap) res5: Option[Int] = None 

It’s kind of like “no patterns”, as you are going to get. At first, messy implicit argument lists are scary, but you get used to them pretty quickly, and their role in collecting different bits of evidence about the types you work with becomes quite intuitive after a little practice.

+9
source share

All Articles