如何定义输出类型取决于输入类型的函数
How to define a function whose output type depends on the input type
给出以下 类:
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")
您可以为请求定义一个共同特征,为响应定义一个共同特征,其中请求类型是为特定响应类型定义的:
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"))
这在 Scala 中是完全可能的,也是完全合理的。例如,这种东西在 Shapeless 中随处可见,类似的东西(但原则性较低)是出现在 Spray 等中的磁铁图案的基础
更新:请注意,以下解决方案假定 "given the following classes" 意味着您不想自己触及案例 classes。如果您不在乎,请参阅下面答案的第二部分。
您需要一种类型 class 将输入类型映射到输出类型:
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
}
然后一些类型 class 实例:
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)
}
}
现在您可以使用此类型 class:
定义 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")
^
你甚至 没有 使用路径依赖类型(你应该能够在类型 class 上有两个类型参数),但是它使使用 process
更好一点,例如您必须明确提供类型参数。
更新:以上所有内容都假定您不想更改案例 class 签名(这绝对没有必要)。不过,如果您愿意更改它们,您可以更简洁地执行此操作:
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)
您更喜欢哪种多态性(参数化或临时)完全取决于您。如果您希望能够描述任意类型之间的映射,请使用类型 class。如果您不关心,或者主动不希望此操作可用于任意类型,请使用子类型化。
给出以下 类:
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")
您可以为请求定义一个共同特征,为响应定义一个共同特征,其中请求类型是为特定响应类型定义的:
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"))
这在 Scala 中是完全可能的,也是完全合理的。例如,这种东西在 Shapeless 中随处可见,类似的东西(但原则性较低)是出现在 Spray 等中的磁铁图案的基础
更新:请注意,以下解决方案假定 "given the following classes" 意味着您不想自己触及案例 classes。如果您不在乎,请参阅下面答案的第二部分。
您需要一种类型 class 将输入类型映射到输出类型:
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
}
然后一些类型 class 实例:
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)
}
}
现在您可以使用此类型 class:
定义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")
^
你甚至 没有 使用路径依赖类型(你应该能够在类型 class 上有两个类型参数),但是它使使用 process
更好一点,例如您必须明确提供类型参数。
更新:以上所有内容都假定您不想更改案例 class 签名(这绝对没有必要)。不过,如果您愿意更改它们,您可以更简洁地执行此操作:
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)
您更喜欢哪种多态性(参数化或临时)完全取决于您。如果您希望能够描述任意类型之间的映射,请使用类型 class。如果您不关心,或者主动不希望此操作可用于任意类型,请使用子类型化。