2016-03-30 55 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

這是完全可能的和在斯卡拉完全合理的事情。舉例來說,這種東西都是無形的,並且類似的東西(但不太原則)是在噴霧中出現的磁鐵圖案的基礎等。

更新:請注意,以下解決方案假定「給定以下課程「意味着你不想自己觸摸案例類。如果您不在意,請參閱下面答案的第二部分。

你會想要一個輸入類型映射到輸出類型的一個類型類:

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) 

其中按要求工作(注意適當的st ATIC類型):

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稍微好一些,如果例如你必須明確地提供類型參數。


更新:上面的一切都假設你不想改變你的案例類簽名(這肯定是沒有必要的)。如果你願意改變他們,但是,你可以多一點簡潔做到這一點:

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)?當一個方法完全不依賴於類型時,參數應該是這種情況,比如List [T]上的length()? – slouc

+0

@slouc從何種意義上說?事實上,例如''right' on'[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

嗯。但是如果我用'AddRequest'參數調用'process',它將返回一個'Response [AddRequest]'。我需要它來返回一個'AddResponse',我可以在'sum'上操作它。 – jvliwanag

+0

對不起 - 把它混合起來,現在修復:當它被調用時顯式地鍵入方法,這段代碼運行成功。 –

+0

嗯。你介意爲'process'寫一個實現嗎? – jvliwanag