Динамическая передача экстракторов для сопоставления с образцом
Я хочу иметь возможность динамически выбирать, какие экстракторы использовать в моем сопоставлении шаблонов классов дел.
Я хочу что-то вроде:
def handleProcessingResult(extract: SomeType) : PartialFunction[Event, State] = {
case Event(someEvent: SomeEvent, extract(handlers)) =>
...
case Event(otherEvent: OtherEvent, extract(handlers)) =>
...
}
Идея состоит в том, что я могу иметь вышеупомянутую частичную функцию, и затем могу использовать ее где угодно, где я знаю, как написать unapply
сопоставить и извлечь handlers
из какого-то шаблона сопоставленного типа.
Если вам интересно, почему мне нужны эти частичные функции, я могу составлять частичные функции общего поведения вместе, чтобы сформировать обработчики для моих состояний в FSM Akka. Это не обязательно, чтобы понять вопрос, но, например:
when(ProcessingState) {
handleProcessingResult(extractHandlersFromProcessing) orElse {
case Event(Created(path), Processing(handlers)) =>
...
}
}
when(SuspendedState) {
handleProcessingResult(extractHandlersFromSuspended) orElse {
case Event(Created(path), Suspended(waiting, Processing(handlers))) =>
...
}
Кажется, это возможно, но я не могу понять, как!
Я попробовал следующие два упрощения:
object TestingUnapply {
sealed trait Thing
case class ThingA(a: String) extends Thing
case class ThingB(b: String, thingA: ThingA) extends Thing
val x = ThingA("hello")
val y = ThingB("goodbye", ThingA("maybe"))
process(x, new { def unapply(thing: ThingA) = ThingA.unapply(thing)})
process(y, new { def unapply(thing: ThingB) = ThingB.unapply(thing).map(_._2.a)})
def process(thing: Thing, extract: { def unapply[T <: Thing](thing: T): Option[String]}) = thing match {
case extract(a) => s"The value of a is: $a"
}
}
Идея в том, что я должен быть в состоянии передать любой подтип Thing и подходящий экстрактор process
, Тем не менее, он не компилируется из-за:
[error] /tmp/proj1/TestUnapply.scala:10: type mismatch;
[error] found : AnyRef{def unapply(thing: TestingUnapply.ThingA): Option[String]}
[error] required: AnyRef{def unapply[T <: TestingUnapply.Thing](thing: T): Option[String]}
[error] process(x, new { def unapply(thing: ThingA) = ThingA.unapply(thing)})
[error] ^
[error] /tmp/proj1/TestUnapply.scala:11: type mismatch;
[error] found : AnyRef{def unapply(thing: TestingUnapply.ThingB): Option[String]}
[error] required: AnyRef{def unapply[T <: TestingUnapply.Thing](thing: T): Option[String]}
[error] process(y, new { def unapply(thing: ThingB) = ThingB.unapply(thing).map(_._2.a)})
[error] ^
Впоследствии, перемещение объявления типа параметра T
на process
, дает нам:
import scala.reflect.ClassTag
object TestingUnapply {
sealed trait Thing
case class ThingA(a: String) extends Thing
case class ThingB(b: String, thingA: ThingA) extends Thing
val x = ThingA("hello")
val y = ThingB("goodbye", ThingA("maybe"))
process(x, new { def unapply(thing: ThingA) = ThingA.unapply(thing)})
process(y, new { def unapply(thing: ThingB) = ThingB.unapply(thing).map(_._2.a)})
def process[T <: Thing: ClassTag](thing: Thing, extract: { def unapply(thing: T): Option[String]}) = thing match {
case extract(a) => s"The value of a is: $a"
}
}
Теперь дает нам другую ошибку компиляции:
[error] /tmp/TestUnapply.scala:18: Parameter type in structural refinement may not refer to an abstract type defined outside that refinement
[error] def process[T <: Thing: ClassTag](thing: Thing, extract: { def unapply(thing: T): Option[String]}) = thing match {
Скорее всего, я делаю что-то глупое. Может кто-то помочь мне, пожалуйста?
1 ответ
Попробуйте сделать обходной путь, основанный на вашем первом упрощении, надеюсь, это поможет.
object DynamicPattern extends App {
sealed trait Thing
case class ThingA(a: String) extends Thing
case class ThingB(b: String, thingA: ThingA) extends Thing
// change structural type to an abstract class
abstract class UniversalExtractor[T <: Thing] {
def unapply(thing: T): Option[String]
}
// extract is an instance of UniversalExtractor with unapply method
// naturally it's an extractor
def process[T <: Thing](thing: T, extract: UniversalExtractor[T]) =
thing match {
case extract(a) => s"The value of a is: $a"
}
val x = ThingA("hello")
val y = ThingB("goodbye", ThingA("maybe"))
val result1 = process(
x,
new UniversalExtractor[ThingA] {
def unapply(thing: ThingA) = ThingA.unapply(thing)
}
)
val result2 = process(y,
new UniversalExtractor[ThingB] {
def unapply(thing: ThingB) = ThingB.unapply(thing).map(_._2.a)
}
)
// result1 The value of a is: hello
println(" result1 " + result1)
// result2 The value of a is: maybe
println(" result2 " + result2)
}
Обновить
Вероятно, "противный" метод без использования абстрактного класса или признака, на который намекает проблема соответствия типов, которую я объяснил позже.
// when invoking process method, we actually know which subtype of
// Thing is pattern-matched, plus the type conformance problem,
// so here comes the ```unapply[T <: Thing](thing: T)```
// and ```asInstanceOf(which is usually not that appealing)```.
val thingAExtractor = new {
def unapply[T <: Thing](thing: T): Option[String] =
ThingA.unapply(thing.asInstanceOf[ThingA])
}
val thingBExtractor = new {
def unapply[T <: Thing](thing: T): Option[String] =
ThingB.unapply(thing.asInstanceOf[ThingB]).map(_._2.a)
}
// hello
println(process(x, thingAExtractor))
// maybe
println(process(y, thingBExtractor))
Причина, по которой это не работает в двух упрощениях (на самом деле, когда я пытаюсь выяснить причину, в моей памяти просто появляется неприятный метод, поэтому просто напишите здесь, если это поможет).
Для первого упрощения: речь идет о type conformance
проблема.
type ExtractType = { def unapply[T <: Thing](thing: T): Option[String] }
val anonExtractor = new { def unapply(thing: ThingA) = ThingA.unapply(thing) }
import scala.reflect.runtime.{ universe => ru }
import scala.reflect.runtime.universe.{ TypeTag, typeTag }
def getTypeTag[T: TypeTag](o: T) = typeTag[T].tpe
def getTypeTag[T: TypeTag] = ru.typeOf[T]
// false | so in turn type check fails at compilation time
println(getTypeTag(anonExtractor) <:< getTypeTag[ExtractType])
Классы выполнения ScalaDoc Reflection в Java и типы времени выполнения в части Scala демонстрируют соответствие типов в аналогичном случае. Короче говоря, компилятор Scala создает синтетические классы, которые используются во время выполнения вместо пользовательских классов для преобразования в эквивалентный байт-код Java в случаях, упомянутых в этой части.
Для второго упрощения: этот пост- параметр типа-в-структуре-уточнение-может-не-ссылаться-на-абстрактный-тип-определить-снаружи дал некоторое подробное объяснение.