Shapeless:由案例类或字段参数化的通用镜头

11

基于:

import shapeless._

case class Content(field: Int)
lens[Content] >> 'field

我正在尝试制作一个镜头创建方法,类似于:

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

但这似乎不太明显。这是否可能做到呢?

如果不行,我试图实现的最终结果是一种通用的方法,用于更新包含case-class内容的嵌套Maps,例如:

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)

我无法通过T和f参数化使其正常工作。 是否有其他免除样板文件的惯用解决方案?

谢谢!

1个回答

12
您的makeLens存在问题,我们希望例如makeLens[Content]('foo)在编译时失败,但是普通的Symbol参数无法实现这一点。您需要一些额外的隐式参数来跟踪给定名称的单例类型,并提供证据表明它是案例类成员的名称。
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]

接着:

scala> case class Content(field: Int)
defined class Content

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

但是makeLens[Content]('foo)无法编译(这正是我们想要的)。

你需要对nestedMapLens进行相同类型的跟踪:

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)
    )
}

请注意,我假设您有以下这样的 build.sbt 文件:
scalaVersion := "2.11.2"

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

现在让我们定义一个示例地图和一些镜头:
val myMap = Map("foo" -> Map(1 -> Content(13)))

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

然后:

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

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

这是尽可能“无样板”的内容。一开始,混乱的隐式参数列表令人望而生畏,但您很快就会习惯它们,并且在实践一段时间后,它们在汇集有关您正在处理的类型的不同证据方面的作用变得相当直观。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接