Динамическая передача экстракторов для сопоставления с образцом

Я хочу иметь возможность динамически выбирать, какие экстракторы использовать в моем сопоставлении шаблонов классов дел.

Я хочу что-то вроде:

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 в случаях, упомянутых в этой части.

Для второго упрощения: этот пост- параметр типа-в-структуре-уточнение-может-не-ссылаться-на-абстрактный-тип-определить-снаружи дал некоторое подробное объяснение.

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