2016-01-04 3 views
4

Учитывая простой признак, потребляющее значение типа T:Как объединить двух потребителей?

trait Consumer[-T] extends (T => Unit) { 
    def +[U, P](other: Consumer[U]): Consumer[P] = ??? 
} 

Как реализовать + функции, которая сочетает в себе два потребителей в один, который принимает действительный супертип для AnyRef или более широкого типа для AnyVal? Для следующих случаев:

-AnyRefs, которые имеют общую супертип:

trait Base 
trait A extends Base 
trait B extends Base 

val c1: Consumer[A] = _ 
val c2: Consumer[B] = _ 
val cc = c1 + c2 //cc must have type of Consumer[Base] 

-AnyVals:

val c1: Consumer[Int] = _ 
val c2: Consumer[Long] = _ 
val cc = c1 + c2 //cc must have type of Consumer[Long] 

ответ

3

Вы пытаетесь реализовать концепцию частичной функции. При этом вам нужно знать, что вы жертвуете безопасностью типа для динамичности. На строго типизированном языке вы обычно не можете делать этого без какого-либо явного взлома, например. смягчение правил приведения типов или дисперсий.

Например, стандартный номер Scala PartialFunction делает это для вас: он позволяет назначить функцию другой функции, которая отключает проверку контравариантности параметра. В качестве примера, имеющей иерархию

trait Animal 
case class Dog() extends Animal 
case class Cat() extends Animal 

типа, это позволяет «назначать» функцию типа Dog => T в зависимости от типа Animal => T (что противоречит правилам контрвариации):

val pf: PartialFunction[Animal, Unit] = { 
    case Dog() => println("dog") 
} 

За счет на данный момент возможны ошибки совпадения во время выполнения:

pf(Cat()) // MatchError 

Таким образом, в целом, чтобы объединить несколько потребителей в Scala вы можете сделать точно такой же трюк Scala язык делает для вас при определении частичных функций - изменение дисперсии потребительских параметров до ковариантного, то есть -T до +T. Чтобы сделать это, используйте @uncheckedVariance аннотацию:

trait Consumer[+T, +V] extends ([email protected] ⇒ V) { 
    def handle(command: [email protected]): V 
} 

Такое отклонение позволяет иметь таблицу поиска потребителей:

val lookup: Map[Class[_], Consumer[Animal]] = Map(
    classOf[Dog] → dogConsumer, 
    classOf[Cat] → catConsumer 
) 

, которые могут быть использованы для реализации отправки на основе (в данном примере случае) тип класса ,

1

Это не возможно, как вы уже предполагали, потому что вы должны гарантировать супертип состоит из только подтипы вы упомянули, или вы будете несостоятельны:

trait A 
trait B extends A 
trait C extends A 
trait D extends A 

val x: Consumer[A] = (new Consumer[B]) + (new Consumer[C]) 
x(new D) // Uh-oh, nobody knows how to handle this! 

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

Например, если вы на самом деле не волнует, есть ли уверенность в том, что что-то будет потребляться, и вы используете только типы, чтобы избежать очевидной глупости, вы можете сделать что-то вроде

abstract class Consumer[A: reflect.ClassTag] { 
    def apply(a: A): Unit 
    def consume(a: Any) { 
    val c = implicitly[reflect.ClassTag[A]].runtimeClass 
    if (c isInstance a) apply((c cast a).asInstanceOf[A]) 
    } 
    def +[A1 >: A : reflect.ClassTag](that: Consumer[A1]) = { 
    new Consumer[A1] { 
     def apply(a1: A1) = { consume(a1); that.consume(a1) } 
    } 
    } 
} 
Смежные вопросы