问题描述
基于:
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.
这篇关于不变形:根据镜盒类别或视场参数化的通用镜片的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!