Scala - 使用泛型类型参数调用方法,给定一个确定正确类型的字符串值

时间:2014-08-26 07:55:08

标签: scala generics

我正在设计一个2层架构的API接口。它从URL获取字符串参数fieldName并返回JSON字符串。 fieldName是指我的数据库表中的一个字段。您可以将其签名视为:

def controller(fieldName: String): String

在控制器中,我想在我的数据访问层中调用一个方法来执行以下查询:

SELECT fieldName, SUM(salary) FROM Employee GROUP BY fieldName

因为字段的类型不同,查询结果的类型也会不同。此方法由泛型类型参数T参数化,该参数对应于名称为fieldName的字段的类型。

def getTotalSalaryByField[T](fieldName: String): Map[T, Long]
  • 如果fieldName为" age",则T应为Int。
  • 如果fieldName是" name",则T应为String。 等等。

在运行时给定一个特定的fieldName,如何调用此方法为其提供正确的类型?

我不想写很多if-else或模式匹配语句来选择类型。它看起来像这样:

fieldName match {
    case "age" => serializeToJson(getTotalSalaryByField[Int]("age"))
    case "name" => serializeToJson(getTotalSalaryByField[String]("name"))
    ...
    // 100 more for 100 more fields
}

这太丑了。如果我用Python编写它,它只需要一行:

json.dumps(getTotalSalaryByField(fieldName))

Scala不适合休息后端编程吗?因为这似乎是人们会遇到的常见模式,静态类型会妨碍。我希望看到一些关于如何以scala-ish方式处理整个问题的建议,即使这意味着重新构建,重写DAL和控制器。

修改: @drexin,DAL方法的实际签名是

def myDAOMethod[T](tf: Option[TimeFilter], cf: CrossFilter)
    (implicit attr: XFAttribute[T]): Map[T, Long]

T是fieldName的类型。 y是y的类型。如您所见,我需要根据url参数中的fieldName选择一个T类型。

修改: 添加了一些代码并使用例明确

4 个答案:

答案 0 :(得分:2)

在编译时和运行时知道变量的类型之间存在差异。

如果在编译时不知道fieldName(即它是参数),并且列的类型变化fieldName,那么您将无法在编译时指定方法的返回类型。

您将需要使用返回AnyRef的DAO方法,而不是使用返回T的方法来指定编译时指定的类型T

旧答案:

数据库访问库可以返回值而无需知道它们的类型,您的代码也需要这样做。

您希望使用DAO方法,该方法采用类型参数T

def myDAOMethod[T](tf: Option[TimeFilter], cf: CrossFilter)
    (implicit attr: XFAttribute[T]): Map[T, Long]

...但是如您所述,您事先并不知道类型T,因此此方法不适用。 (T用于将数据库列数据转换为String或Int)。

你的DAO应该提供这种方法的无类型版本,更像是:

def doSelect(tf: Option[TimeFilter], cf: CrossFilter): Map[AnyRef, Long]

您使用的是什么数据库访问库?

答案 1 :(得分:2)

如果您使用Play框架,那么我建议您使用Play JSON API。

https://www.playframework.com/documentation/2.1.1/ScalaJson

而不是

def getTotalSalaryByField[T](fieldName: String): Map[T, Long] = ???

你可以写

def getTotalSalaryByField(fieldName: String): Map[JsValue, Long] = ???

因为JsNumberJsStringJsNull等所有类型都是从通用JSON特征JsValue继承的。

Json.toJson()

包装queryResult

def getTotalSalaryByField(fieldName: String): JsObject = ???

答案 2 :(得分:1)

我认为你有三种选择:

第三个选项可能正是您所寻找的,但它基于实验性scala功能(即宏)。我想它将要做的是在编译阶段连接到您的数据库并检查模式。如果您使用一些单独的sql文件生成模式,那么它应该更容易。然后宏将生成所有样板的硬编码。

您可能无法找到确切需要的工作示例,但有一个可用作灵感的RFC文件:https://github.com/travisbrown/type-provider-examples

答案 3 :(得分:0)

我不是100%确定我理解你的问题,如果我完全回答其他问题,请道歉。

您需要Scala才能根据预期的返回类型猜测字段的类型。也就是说,在以下代码中:

val result : Map[String, Long] = myDAOMethod(tf, cf)

您希望Scala正确推断,因为您需要Map[String, Long],所以x变量的类型为T

在我看来,你已经拥有了所需的一切。我不知道XFAttribute[T]是什么,但我怀疑它允许您将结果集中的条目转换为T类型的实例。

除此之外,您已经将其声明为隐含参数。

如果范围内有隐式XFAttribute[String],则前面的代码应该编译,运行并且是类型安全的。

作为一项小改进,我改变了方法的签名以使用上下文界限,但这主要是品味问题:

// Declare the implicit "parsers"
implicit val IntAttribute: XFAttribute[Int] = ???
implicit val StringAttribute: XFAttribute[String] = ???

// Empty implementation, fill it with your actual code.
def myDAOMethod[T: XFAttribute](tf: Option[TimeFilter], cf: CrossFilter): Map[T, Long] = ???

// Scala will look for an implicit XFAttribute[Int] in scope, and find IntAttribute.
val ages: Map[Int, Long] = myDAOMethod(tf, cf)

// Scala will look for an implicit XFAttribute[String] in scope, and find StringAttribute.
val names: Map[String, Long] = myDAOMethod(tf, cf)

我不确定您的问题是否意味着您还希望将字符串"age"强烈绑定到类型Int。当然,这也是可能的,但它完全是另一个答案,我不想用不必要的漫无边际污染这个问题。