动态传递提取器以进行模式匹配

Passing extractors dynamically for pattern matching

我希望能够动态选择在我的案例中使用哪些提取器class 模式匹配。

我想要这样的东西:

def handleProcessingResult(extract: SomeType) : PartialFunction[Event, State] = {
    case Event(someEvent: SomeEvent, extract(handlers)) =>
        ...

    case Event(otherEvent: OtherEvent, extract(handlers)) =>
        ...
}

我的想法是,我可以拥有上述部分功能,然后可以在我知道如何编写 unapply 的任何地方使用它来匹配并从某种模式匹配类型中提取 handlers

如果您想知道为什么我需要这些部分函数,​​那么我可以将常见行为的部分函数组合在一起,形成 Akka FSM 中状态的处理程序。这不是理解问题所必需的,但是例如:

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 {

我很可能在做一些愚蠢的事情。有人可以帮我吗?

尝试根据您的第一个简化来解决问题,希望对您有所帮助。

 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)
}

更新

一个可能 "nasty" 的方法,没有使用抽象 class 或我稍后解释的类型一致性问题暗示的特征。

  // 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 ReflectionRuntime 类 in Java vs. Runtime Types in Scala 部分演示了类似情况下的类型一致性。简而言之,Scala 编译器创建合成的 classes,在运行时代替用户定义的 classes,在该部分提到的情况下被翻译成等效的 Java 字节码。

对于第二个简化:这个postparameter-type-in-structural-refinement-may-not-refer-to-an-abstract-type-define-outside已经给出了一些详细的解释