2015-05-30 9 views
0

Я не уверен, что название описывает мой вопрос лучше всего, но давайте сделаем это.Общие аргументы метода в scala

У меня есть приложение для выполнения фоновых заданий, которое напоминает простую обработку конвейера. Есть Command объектов, которые делают некоторые вычисления и возвращают OUTPUT и Worker, которые получают OUTPUT в качестве входных данных и может вернуться Result объектная модель выглядит примерно так:

type OUTPUT <: AnyRef 
trait Command[OUTPUT] { 
    def doSomething(): OUTPUT 
} 
sealed trait Worker[IN <: AnyRef, OUT <: Result] { 
    def work(input: IN): OUT 
} 

case class WorkA() extends Worker[String, LongResult] { 
    override def work(input: String): LongResult = LongResult(Long.MaxValue) 
} 

case class WorkB() extends Worker[Long, StringResult] { 
    override def work(input: Long): StringResult = StringResult(input.toString) 
} 

Есть несколько проблем с этим подходом:

При сопоставлении коллекции Worker я не могу убедиться, что рабочий принимает тот же OUTPUT в качестве входных данных.

Если я не буду отображения списка Command, код не компилируется из-за типа стирания - это ожидает _$1, но получает Long или в String (все, что было ранее принято в качестве OUTPUT)

val workers = List(
    new WorkA(), 
    new WorkB() 
) 

val aSimpleCommand = new Command[Long] { 
    override def doSomething() = 123123123L 
} 
// Obviously this doesn't compile. 
workers.map(worker => worker.work(aSimpleCommand.doSomething())) 

Я ищу правильный механизм Scala, чтобы запретить это во время компиляции. Как я могу отобразить только на работнике, которые фактически поддерживают OUTPUT - и в этом случае, только WorkB

+0

Здесь вы не можете понять. В списке «worker» есть 1 работник, который принимает «String» и другой рабочий, который принимает «Long». 'aSimpleCommand.doSomething' возвращает' Long', но используя 'map', вы пытаетесь передать его' WorkA', который хочет 'String'. Конечно, он не может скомпилироваться. – Kolmar

+0

Точно @ Kolmar. Я ищу правильный механизм Scala, чтобы запретить это во время компиляции. Как я могу отображать ТОЛЬКО на «Рабочем», который действительно поддерживает «OUTPUT» - и в этом случае только «WorkB» – yarinbenado

+1

Я думаю, что точка зрения Колмара заключается в том, что это уже не разрешено во время компиляции - я предполагаю, что вы имеете в виду, как это сделать вы запрещаете его в _runtime_ (т. е. скомпилируйте его с радостью, но отфильтровывайте ненадлежащих сотрудников во время выполнения)? – Shadowlands

ответ

1

Если вы хотите сделать это во время компиляции вы можете использовать бесформенный HLists, сохраняя тип вашего списка всего пути до конца, а затем с помощью Poly обрабатывать случаи:

val myWorkers: WorkA :: WorkB :: WorkB :: HNil = 
    WorkA() :: WorkB() :: WorkB() :: HNil 

object doWork extends Poly1 { 
    implicit def caseLong[A] = at[Worker[Long, A]] { 
    w => w.work(aSimpleCommand.doSomething())} 
    implicit def caseString = //whatever you want to do with a String worker 
} 

myWorkers map doWork 

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

val myWorkers: List[Worker[_, _]] = ... 
myWorkers collect { 
    case wa: WorkA => //works 
    case lw: Worker[Long, _] => //doesn't work 
} 
+0

Shapeless - это потрясающе! Благодарю. – yarinbenado

0

Если это возможно, чтобы явно указать все классы, которые расширяют Worker[Long, _], то вы могли бы сопоставить работник по частичной функции, как так :

val res = workers map { 
    case b: WorkB => Some(b.work(aSimpleCommand.doSomething())) 
    case _ => None 
} 

В вашем примере это вернет List(None, Some(StringResult(123123123)). Кроме того, можно собирать только существующие значения:

res collect {case Some(r) => r} // List(StringResult(123123123)) 

Теперь, это не очень практичным решением. Возможно, следующие мысли помогут вам придумать что-то лучшее:

Как вы уже заявили, из-за стирания типа мы не можем создать нашу частичную функцию, чтобы принимать значения типа Worker[Long, _] во время выполнения. ClassTagTypeTag) обеспечивают решение этой проблемы, позволяя компилятору создавать доказательства, доступные во время выполнения для стираемых типов. Например, следующая функция извлекает класс выполнения ввода работника: использования

import scala.reflect.ClassTag 
def getRuntimeClassOfInput[T: ClassTag](worker: Worker[T, _]) = implicitly[ClassTag[T]].runtimeClass 

Пример:

println(getRuntimeClassOfInput(new WorkB)) // long 

Проблема заключается в том, что это не похоже на работу, как только рабочие внутри списка , Я полагаю, что это связано с тем, что после того, как в списке есть несколько разных работников, список становится List[Worker[Any, Result] и вы теряете всю информацию о типе. Что такое может решить эту проблему: Heterogenous Lists, потому что в отличие от стандартных списков они сохраняют информацию о статическом типе всех элементов.