2014-07-30 21 views
8

na podstawie:Shapeless: obiektyw generic parametryzowane przez klasę case lub pola

import shapeless._ 

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

Próbuję wykonać metodę obiektywu, tworząc coś razem:

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

Ale wydaje nieoczywisty . Czy można to zrobić?

Jeśli nie, to końcowy wynik staram się osiągnąć to ogólna metoda aktualizacji zagnieżdżone Mapy z treścią przypadek klasy, np:

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) 

nie mogę zmusić go do pracy, gdy parametryzowane przez T i fa. Czy istnieją inne idiomowe rozwiązania bez użycia kartek?

Dzięki!

Odpowiedz

9

Problem z Twoim makeLens polega na tym, że chcemy np. makeLens[Content]('foo), aby zakończyć się niepowodzeniem podczas kompilacji, a to nie jest możliwe przy zwykłym argumencie Symbol. Trzeba jakieś dodatkowe ukryte argumenty śledzić typu singleton dla danej nazwy i przedstawienia dowodów, że jest to nazwa członka klasy obudowy:

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] 

, a następnie:

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

scala> makeLens[Content]('field) 
res0: shapeless.Lens[Content,Int] = [email protected] 

Ale makeLens[Content]('foo) nie będzie się kompilował (a tego chcemy).

Trzeba ten sam rodzaj śledzenia dla 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) 
    ) 
} 

Zauważ, że jestem zakładając build.sbt takiego:

scalaVersion := "2.11.2" 

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

Teraz zdefiniować przykładową mapę i kilka obiektywów:

val myMap = Map("foo" -> Map(1 -> Content(13))) 

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

A następnie:

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

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

Chodzi o to, że "nie masz dostępu do tablicy", jak masz zamiar dostać. Brudne, niejawne listy argumentów są na początku zastraszające, ale dość szybko przyzwyczajasz się do nich, a ich rola w łączeniu różnych dowodów na temat typów, z którymi pracujesz, staje się dość intuicyjna po krótkiej praktyce.