【问题标题】:Scala: determine method result type for use in genericsScala:确定用于泛型的方法结果类型
【发布时间】:2019-04-24 11:15:41
【问题描述】:

在第三方库中有一系列的请求类,它们都派生自一些通用的基类,是泛型的,以响应类为参数:

abstract class AbstractRequest[ResponseType] {
  …
  def execute(): ResponseType
}

class UserList {…}
class UserListRequest extends AbstractRequest[UserList] {…}

class Avatar {…}
class AvatarRequest extends AbstractRequest[Avatar] {…}

…

我想编写一些通用方法,它接受一个请求实例,以一些特殊的方式执行它多次,并将响应的处理委托给参数中提供的函数:

def specialMultiExecute(request: Req)(responseHandler: Resp => Unit): Unit = …

——被称为:

val myRequest: UserListRequest = …
specialMultiExecute(myRequest){ userList => … }

问题是我需要在specialMultiExecute 声明中以某种方式指定ReqResp 类型。我尝试了明显的方法:

def specialMultiExecute[Req <: AbstractRequest[Resp], Resp](request: Req)(responseHandler: Resp => Unit): Unit = …

——但 Scala 编译器无法推断出通用参数类型(需要像 specialMultiExecute[UserListRequest, UserList](myRequest){ userList =&gt; … } 这样的显式规范)。

在这种情况下,在 C++ 中,我可以使用单个模板参数Req 编写模板函数,同时将Resp 确定为方法Req::execute结果类型: p>

template<typename Req>
void specialMultiExecute(
    Req request,
    std::function<void (decltype(std::declval<Req>().execute()))> responseHandler
) {…}
//i.e. we use `decltype(std::declval<Req>().execute())` instead of Resp

有没有办法写出类似Scala的东西?

我的意思是(在类似 Scala 的伪代码中):

def specialMultiExecute[Req <: abstractrequest req>ResultTypeOf(Req#execute) => Unit): Unit = …

【问题讨论】:

  • 您需要知道您拥有AbstractRequest 的哪个子类型,还是只知道您拥有哪个ResponseType 就可以了吗?
  • @LuisMiguelMejíaSuárez,说实话,specialMultiExecute 似乎最好知道AbstractRequest 的确切子类型。 (从上面的解释来看,这可能并不明显,但它与specialMultiExecute 内部如何工作的一些未指定的脏细节有关。)但是,如果我理解正确,您的第一个示例涵盖了这种情况(我现在正在尝试阅读并了解其工作原理)。

标签: scala generics


【解决方案1】:

这是类型推断机制的限制。 解决它的最简单方法是使用隐含证据证明ReqAbstractRequest[ResponseType]子类型

这是一个例子。

import scala.language.implicitConversions
import scala.reflect.runtime.universe.TypeTag

abstract class AbstractRequest[ResponseType] {
  def execute(): ResponseType
}

final case class User(id: Int, name: String)
final case class House(id: Int, price: Int)

class UserListRequest extends AbstractRequest[List[User]] {
  override def execute(): List[User] = List(User(id = 3, name = "Sasha"))
  override def toString: String = "UserListRequest"
}

final class RequestWrapper[Req, Resp](val request: Req) extends AnyVal {
  type ResponseType = Resp
}

implicit def request2wrapper[Req, Resp](request: Req)(implicit ev: Req <:< AbstractRequest[Resp]): RequestWrapper[Req, Resp] =
  new RequestWrapper(request)

def specialMultiExecute[Req, Resp](wrapper: RequestWrapper[Req, Resp])
                                  (responseHandler: wrapper.ResponseType => Unit)
                                  (implicit ev: Req <:< AbstractRequest[Resp], TTReq: TypeTag[Req], TTResp: TypeTag[Resp]): Unit = {
  val request: Req = wrapper.request
  val executionResult: Resp = request.execute()
  responseHandler(executionResult)
  println(TTReq)
  println(TTResp)
  println(request)
}

specialMultiExecute(new UserListRequest())(println)
// List(User(3,Sasha))
// TypeTag[UserListRequest]
// TypeTag[List[User]]
// UserListRequest

Reference&lt;:&lt;Reference 代表“依赖类型”

编辑

修改了上述代码示例,以允许识别正在使用的具体 RequestResponse 类型。

【讨论】:

  • 虽然您的回答没有直接回答我的问题(即是否有可能以可用于泛型的方式获取方法结果类型),但我从中获得了许多其他有用的信息。因此我接受它。 (此外,我找到了一种方法,可以在不知道确切的 Req 类型的情况下使specialMultiExecute内部细节工作。)
  • 嗨@Sasha,很高兴它能帮助你。我认为第一个代码 sn-p 为您提供了所需的所有信息。 (例如 ReqResp 的确切类型),还是我遗漏了什么?
  • 在第一个 sn-p 中,Req[ResponseType]AbstractRequest[List[User]],而不是 UserListRequest。我们可以检查,例如,通过println(scala.reflect.runtime.universe.typeOf[Req[ResponseType]])
  • @Sasha 啊,你是对的。原因是 UserListRequest 与签名类型 Req[_] 不匹配,因为它不接受任何类型参数——我的错,对不起。我想我可以修复它,并在完成后更新答案。
  • 谢谢。实际上,现在仍然需要在调用期间指定响应类型,尽管它已经从 [UserListRequest, List[User]] 移动到 l: List[User](但是,现在我们只需要指定 Resp,而不是同时指定 @ 987654341@ 和 Resp)。无论如何,如前所述,我已经找到了一种方法让specialMultiExecute 在不知道确切的Req 类型的情况下工作,所以现在只是为了满足我对研究的渴望。
猜你喜欢
  • 2019-12-28
  • 2012-09-08
  • 2011-06-26
  • 2017-09-08
  • 1970-01-01
  • 2012-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多