不变形:根据镜盒类别或视场参数化的通用镜片 [英] Shapeless: generic lens parameterized by case class or field

查看:94
本文介绍了不变形:根据镜盒类别或视场参数化的通用镜片的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

基于:

import shapeless._

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

我正在尝试创建一种镜头创建方法,

I am trying to make a lens-creating method, something along:

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

但是似乎并不明显.有可能吗?

But it seems non-obvious. Is it possible to do?

如果没有,我想要达到的最终结果是一种通用的方法,用于更新具有case类内容的嵌套Maps,例如:

If not, the end result I'm trying to achieve is a generic method for updating nested Maps with case-class contents, e.g.:

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参数化时,我无法使其工作.还有其他无惯用的无样板解决方案吗?

I cannot get it to work when parameterized by T and f. Are there any other idiomatic boilerplate-free solutions?

谢谢!

推荐答案

您的makeLens的问题是我们想要makeLens[Content]('foo)在编译时失败,这对于普通的Symbol参数是不可能的.您需要一些额外的隐式参数来跟踪给定名称的单例类型,并提供证据证明它是case类成员的名称:

The problem with your makeLens is that we want e.g. makeLens[Content]('foo) to fail at compile time, and that's not possible with an ordinary Symbol argument. You need some extra implicit arguments to track the singleton type for the given name and to provide evidence that it's 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]

然后:

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)无法编译(这是我们想要的).

But makeLens[Content]('foo) won't compile (which is what we want).

您需要对nestedMapLens进行相同的跟踪:

You need the same kind of tracking for your 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:

Note that I'm assuming a build.sbt like this:

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)

然后:

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

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

这与您将获得的无样板"差不多.起初凌乱的隐式参数列表令人生畏,但是您很快就习惯了它们,经过一点练习后,它们在收集有关正在使用的类型的不同证据方面的作用变得相当直观.

This is about as "boilerplate-free" as you're going to get. The messy implicit argument lists are intimidating at first, but you get used to them pretty quickly, and their role in pulling together different bits of evidence about the types you're working with becomes fairly intuitive after a little practice.

这篇关于不变形:根据镜盒类别或视场参数化的通用镜片的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆