2015-08-11 4 views
0

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

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

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

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

Идея заключается в том, что у меня может быть выше частичную функцию, а затем можно использовать в любом месте, где я знаю, как написать unapply, чтобы соответствовать и извлечь handlers из какой-то шаблон согласованный тип.

Если вам интересно, почему я хочу эти частичные функции, это значит, что я могу составлять частичные функции общего поведения вместе, чтобы сформировать обработчики для моих состояний в FK 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 { 

Я, скорее всего, делаю что-то глупое. Может кто-то мне помочь, пожалуйста?

ответ

0

Попробуйте сделать обходной путь на основе вашего первого упрощения, надеясь, что это поможет.

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

Update

, вероятно, «неприятный» метод без использования абстрактного класса или признака намекают проблемы типа соответствия я объяснил позже.

// 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 «s среда выполнение Классов в Java против времени выполнения типов в Scala часть демонстрирует тип соответствие в подобном случае. Короче говоря, Scala-компилятор создает синтетические классы, которые используются во время выполнения вместо пользовательских классов, которые должны быть переведены в эквивалентный Java-код в случаях, упомянутых в этой части.

Для второго упрощения: этот пост parameter-type-in-structural-refinement-may-not-refer-to-an-abstract-type-define-outside дал подробное объяснение.

+0

Спасибо, да, используя абстрактный класс или черту, было о лучшем, что я мог бы придумать. Я надеялся, что есть лучший способ. Я оставлю это открытым еще пару дней, и если дальнейших предложений нет, тогда я буду принимать это как ответ. – adamretter

+1

@adamretter, эй, просто обновите свой ответ. Поверьте, вам не нужно принимать ответ. Я просто попал в ловушку от того, почему ваши два упрощения не могут работать. После некоторого продуктивного исследования, я получил его и , тогда казалось бы, неприятное решение просто появилось без предупреждения. В любом случае, надеюсь, что это поможет. –

+0

Спасибо @ allen-chou Мне очень нравится ваше объяснение, и, прочитав связанные статьи, яснее. Хорошая вещь :-) – adamretter

Смежные вопросы