【问题标题】:Scala class with generic types具有泛型类型的 Scala 类
【发布时间】:2020-03-27 15:54:57
【问题描述】:

我正在尝试围绕 Java Aws Lambda 客户端编写一个精简的 Scala 包装器。

这个类应该接受 2 个通用参数:

  • 输入类型 A - 应该是 case 类,它被序列化为 Json 并发送到 Lambda 函数,或者是 Nothing/Unit 类型,以防打算调用的 Lambda 函数没有接受任何输入参数。
  • 输入类型 B - 应该是一个 case 类,它是 Lambda 函数返回的实际反序列化 Json->case 类,或 Unit,以防 Lambda 函数不返回任何内容。

类似的东西:

import com.amazonaws.services.lambda.model.InvokeRequest
import com.amazonaws.services.lambda.{AWSLambda, AWSLambdaClientBuilder}
import org.json4s.native.Serialization
import org.json4s.native.Serialization.{read, write}

class LambdaInvoker[A <: AnyRef, B <: AnyRef](val client: AWSLambda = AWSLambdaClientBuilder.defaultClient()) {

  implicit val serialization: Serialization.type = org.json4s.native.Serialization

  def call(input: A, function: String): B = {

    val request = new InvokeRequest().withFunctionName(function).withPayload(write(input))
    val result = client.invoke(request)
    val rawJsonResponse = new String(result.getPayload.array(), "UTF-8")
    read(rawJsonResponse)
  }
}

当我有调用的输入和输出时,这很好用,但不知道什么是最好的“Scala”处理方式,当 A 或 B 不应该出现时。我一直在寻找获取 A 或 B 的运行时类型的方法,检查 Unit,然后以此为基础建立逻辑,但找不到明显的方法(可能是由于类型擦除?)

如果我可以在这里应用不同的模式,没有泛型但使用 Optionals,或者任何其他实现相同目标的东西,那也很棒。

【问题讨论】:

  • 这是typeclass 的绝佳用例——基本上我会写类似class LambdaInvoker[A : Encoder, B : Decoder] 的东西。
  • UnitAnyVal 的子类型,而不是AnyRef 的子类型。
  • 注意:Json4s 是vulnerable under DoS/DoW attacks!

标签: scala generics


【解决方案1】:

这是一个简化的示例,说明如何使用 typeclass 模式。我要定义一个本地的,负责写入和读取一个对象

object LambdaInvoker {
  trait RW[A] {
    def addPayload(a: A, r: InvokeRequest): InvokeRequest
    def readResponse(r: InvokeResult): A
  }

现在,我们需要用实际实现定义一些实例(作为隐式实例)。我们有两种情况:要么是 Unit,然后我们知道如何具体处理它,要么是 JSON 可编码的东西。

为了使一切正常工作,我们必须使用 隐式优先级 技术,将后备放置到我们扩展的单独特征/类中。这是一种处理Unit 的方法,根本不做任何事情:

  object RW extends RWFallback {
    // special cases go into this object
    implicit val unitRW: RW[Unit] = new RW[Unit] {
      def addPayload(a: Unit, r: InvokeRequest) = r
      def readResponse(r: InvokeResult) = ()
    }
  }

我们的后备将使用 json4s。 Json4s 需要一个叫做 Manifest 的东西,它是遗留的 scala 反射 API 的一部分,我们可以只需要它,而不局限于 AnyRef

  trait RWFallback {
    // fallback to json4s if not special cased
    implicit def fallbackRW[A: Manifest]: RW[A] = new RW[A] {
      implicit val serialization: Serialization.type = org.json4s.native.Serialization
      implicit val formats = org.json4s.DefaultFormats

      def addPayload(a: A, r: InvokeRequest) = r.withPayload(write(a))
      def readResponse(r: InvokeResult) = read(new String(r.getPayload.array(), "UTF-8"))
    }
  }

这一切都在 object LambdaInvoker 中,因为它仅限于 LambdaInvoker 的东西。

}

现在,为了实现,您必须执行以下操作:

  • 要求参与数据类型的 RW 实例(使用隐式参数列表或上下文绑定语法完成,例如 A: LambdaInvoker.RW
  • 在逻辑会因类型而异的地方委派给他们:
class LambdaInvoker[A: RW, B: RW](val client: AWSLambda = AWSLambdaClientBuilder.defaultClient()) {
  // "summon" the implicit values and give them a name, so we can refer to them
  private[this] val rwA = implicitly[RW[A]]
  private[this] val rwB = implicitly[RW[B]]

  def call(input: A, function: String): B = {
    // delegate serialization
    val request = rwA.addPayload(input, new InvokeRequest().withFunctionName(function))
    val result = client.invoke(request)
    // delegate parsing
    rwB.readResponse(result)
  }
}

【讨论】:

  • 一个问题是 json4s 在调用 write() 时需要一个 AnyRef 类型,而这似乎与 Manifest 类型不相符。 json4s的write签名为:def write[A <: anyref w writer a out:>
  • @cristi.calugaru 可能是。好像native包里的那个不一样,不过我没用json4s,不知道好不好用。如果需要,您可以使用&lt;: AnyRef 作为您的界限,这里的重点是更多地展示这个想法。
  • 是的,我最终做到了。我认为这看起来很棒 - 我会稍微调整一下然后回来。
  • 总的来说,这让我走上了正确的道路,这是一个很好的榜样,因此接受了回应。附带说明一下,来自 java 背景、隐式、伴生对象和其他 scala 编译器的好东西确实感觉很神奇:)
猜你喜欢
  • 2015-07-09
  • 2018-10-23
  • 1970-01-01
  • 1970-01-01
  • 2019-02-02
  • 2021-08-22
  • 1970-01-01
  • 2017-07-14
  • 1970-01-01
相关资源
最近更新 更多