Бесформенный: общий объектив, параметризованный по классу дела или полю

На основании:

import shapeless._

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

Я пытаюсь сделать метод создания линз, что-то вроде:

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

Но это кажется неочевидным. Можно ли это сделать?

Если нет, то конечный результат, которого я пытаюсь достичь, - это общий метод обновления вложенных Карт с содержимым класса дел, например:

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 ответ

Решение

Проблема с вашим makeLens что мы хотим, например, makeLens[Content]('foo) потерпеть неудачу во время компиляции, а это невозможно с обычным Symbol аргумент. Вам нужны дополнительные неявные аргументы для отслеживания типа синглтона для заданного имени и для подтверждения того, что это имя члена класса case:

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

Это примерно так же "без шаблонов", как вы собираетесь получить. Грязные неявные списки аргументов поначалу пугают, но вы довольно быстро привыкаете к ним, и после небольшой практики их роль в сборе различных данных о типах, с которыми вы работаете, становится довольно интуитивной.

Другие вопросы по тегам