2016-03-30 5 views
17

Учитывая следующие классы:Как определить функцию, тип вывода зависит от типа входного

case class AddRequest(x: Int, y: Int) 
case class AddResponse(sum: Int) 
case class ToUppercaseRequest(str: String) 
case class ToUppercaseResponse(upper: String) 

Как определить в типизированного образом некоторую функцию:

def process(req: ???): ??? 

такой, что следующая должен справедливо:

val r1: AddResponse = process(AddRequest(2, 3)) 
val r2: ToUppercaseResponse = process(ToUppercaseRequest("aaa")) 

Кроме того, следующий должен не компиляции:

val r3 = process("somestring") 

ответ

27

Это и вполне возможно, и вполне разумно, что нужно сделать в Scala. Например, это нечто совершенно бесформенное, и что-то подобное (но менее принципиальное) является основой модели магнита, которая появляется в спрее и т. Д.

Обновление: обратите внимание, что в следующем решении предполагается, что «данный следующие классы "означает, что вы не хотите касаться самих классов случаев. Если вам все равно, см. Вторую часть ответа ниже.

Вы хотели класс типа, который отображает входные типы, типы выходов:

case class AddRequest(x: Int, y: Int) 
case class AddResponse(sum: Int) 
case class ToUppercaseRequest(str: String) 
case class ToUppercaseResponse(upper: String) 

trait Processable[In] { 
    type Out 
    def apply(in: In): Out 
} 

А потом некоторые типа экземпляров класса:

object Processable { 
    type Aux[I, O] = Processable[I] { type Out = O } 

    implicit val toUppercase: Aux[ToUppercaseRequest, ToUppercaseResponse] = 
    new Processable[ToUppercaseRequest] { 
     type Out = ToUppercaseResponse 
     def apply(in: ToUppercaseRequest): ToUppercaseResponse = 
     ToUppercaseResponse(in.str.toUpperCase) 
    } 

    implicit val add: Aux[AddRequest, AddResponse] = 
    new Processable[AddRequest] { 
     type Out = AddResponse 
     def apply(in: AddRequest): AddResponse = AddResponse(in.x + in.y) 
    } 
} 

И теперь вы можете определить process с помощью этого класса типа :

def process[I](in: I)(implicit p: Processable[I]): p.Out = p(in) 

Что подходит по желанию (обратите внимание на соответствующую страницу АИКТ типов):

scala> val res: ToUppercaseResponse = process(ToUppercaseRequest("foo")) 
res: ToUppercaseResponse = ToUppercaseResponse(FOO) 

scala> val res: AddResponse = process(AddRequest(0, 1)) 
res: AddResponse = AddResponse(1) 

Но это не работает на произвольных типов:

scala> process("whatever") 
<console>:14: error: could not find implicit value for parameter p: Processable[String] 
     process("whatever") 
      ^

Вы даже не имеют использовать зависимый тип пути (вы должны быть в состоянии просто иметь два типа типа в классе типов), но он делает использование process немного лучше, если, например, вы должны явно указать параметр типа.


Обновление: все вышеперечисленное предполагает, что вы не хотите менять подписи класса case (что определенно не обязательно).Если вы готовы изменить их, хотя, вы можете сделать это немного более сжато:

trait Input[Out] { 
    def computed: Out 
} 

case class AddRequest(x: Int, y: Int) extends Input[AddResponse] { 
    def computed: AddResponse = AddResponse(x + y) 
} 
case class AddResponse(sum: Int) 

case class ToUppercaseRequest(str: String) extends Input[ToUppercaseResponse] { 
    def computed: ToUppercaseResponse = ToUppercaseResponse(str.toUpperCase) 
} 
case class ToUppercaseResponse(upper: String) 

def process[O](in: Input[O]): O = in.computed 

А потом:

scala> process(AddRequest(0, 1)) 
res9: AddResponse = AddResponse(1) 

scala> process(ToUppercaseRequest("foo")) 
res10: ToUppercaseResponse = ToUppercaseResponse(FOO) 

Какой вид полиморфизма (параметрический или одноранговая) следует отдавать предпочтение полностью зависит от вас. Если вы хотите описать сопоставление между произвольными типами, используйте класс типа. Если вам все равно или активно не хотят, чтобы эта операция была доступна для произвольных типов, используйте подтипирование.

+0

Не второй случай [политизма подтипов] (https://en.wikipedia.org/wiki/Polymorphism_ (computer_science) #Subtyping) вместо [параметрического полиморфизма] (https://en.wikipedia.org/ вики/Polymorphism_ (computer_science) #Parametric_polymorphism)? Параметрический параметр должен иметь место, когда метод не зависит от типа вообще, например length() в List [T]? – slouc

+0

@slouc В каком смысле? Тот факт, что, например, 'right' на' Либо [L, R] 'возвращает' RightProjection [L, R] 'пример параметрического полиморфизма, а не подтипа. –

+0

Я имею в виду, что 'AddRequest' и' ToUppercaseRequest' расширяют 'Input [T]', метод 'process' принимает экземпляр' Input' и фактически передается подклассам 'Input' (а именно' AddRequest' и 'ToUppercaseRequest'). Это подтип, не так ли? – slouc

3

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

trait Request[R <: Response] 
trait Response 

case class AddRequest(x: Int, y: Int) extends Request[AddResponse] 
case class AddResponse(sum: Int) extends Response 
case class ToUppercaseRequest(str: String) extends Request[ToUppercaseResponse] 
case class ToUppercaseResponse(upper: String) extends Response Response[ToUppercaseRequest] 

Затем process подпись будет:

def process[A <: Request[B], B <: Response](req: A): B 

Когда вы звоните process, вы должны явно определить типы так, что возвращаемый тип, что вы ехр ЭСТ это будет - он не может быть выведено достаточно конкретно:

val r1: AddResponse = process[AddRequest, AddResponse](AddRequest(2, 3)) 
val r2: ToUppercaseResponse = process[ToUppercaseRequest, ToUppercaseResponse](ToUppercaseRequest("aaa")) 
+0

Хм. Но если я вызову 'process' с аргументом' AddRequest', он вернет 'Response [AddRequest]'. Мне нужно, чтобы он вместо этого возвращал «AddResponse», из которого я могу работать на 'sum'. – jvliwanag

+0

Извините - смешайте его, исправлено: с явным вводом метода при его вызове этот код выполняется успешно. –

+0

Hm. Не могли бы вы также написать реализацию для процесса? – jvliwanag