【问题标题】:Defining generic type based on method (structural typing)基于方法定义泛型类型(结构类型)
【发布时间】:2019-12-28 17:26:18
【问题描述】:

我正在尝试声明一个基于某些函数的类型的类。

class A{
  def getC = 10
}

class B {
  def getC = 100
  def getB = 9
}


def readC[T <: {def getC():Int}](obj:T) = {
  obj.getC()
}

val a = new A()
val b  = new B()

readC(a)
readC(b) 

我希望 readC 应该与 A 和 B 一起使用。我也无法对 A 和 B 类进行更改,因此这里没有基于 trait 的解决方案。

还有,有没有更好的方法呢?

【问题讨论】:

  • Trait 可以与 adapter pattern 结合使用

标签: scala typeclass type-constraints duck-typing structural-typing


【解决方案1】:

问题是

def getC = 100

与

的函数签名不同
def getC() = 100

A 和 B 有 getC 没有 () 所以你需要从类型约束中删除 ():

def readC[T <: {def getC: Int}](obj: T) = {
  obj.getC
}

更好的方法是使用typeclass。在线查看有关如何执行此操作的许多优秀教程。

【讨论】:

  • @AbhijitSingh 我认为这只是因为您在def getC 的两个实现中也没有()。这需要匹配。
  • @蒂姆。是的,这就是为什么他们需要从类型约束中删除(),以便它匹配A 和B(不能更改)。幸运的是,这很简单。例如,如果方法名称不同,则需要创建一个类型类,如此处的另一个答案所示。
  • 对于任何对性能感兴趣的人来说,这可能比类型类替代方案(至少在 JVM 上)慢很多,因为实现必须依赖于反射速度慢是出了名的。您应该谨慎使用结构类型,编译器会通过让您导入特定的 scala.language.reflectiveCalls 标志来提醒您这一点。
【解决方案2】:

您正在尝试做的事情称为结构类型,它受 Scala 支持。因为没有实际的类,JVM 上的实现必须依赖反射,所以调用getC 与基于 trait 的解决方案相比会很慢。在这些情况下调用 getC 称为反射调用,编译器实际上会发出警告,除非您 import scala.language.reflectiveCalls 承认您知道自己在做什么。

在我看来,有一个更优雅的解决方案,它依赖于通常称为类型类的东西。您将定义一个 HasC 特征来定义“拥有 C”的含义,然后您将为 A 和 B 提供实现。因为HasC 是你的特质,即使你无法控制A 和B 的实现,你也可以这样做。然后,您将定义readC 采用T 隐式可用的任何类型HasC[T]。 Scala 通过上下文边界支持这一点:def readC[T: HasC]。

这是一个工作示例:

class A {
  def getCFromA: Int = 10
}

class B {
  def getCFromB: Int = 100
}

trait HasC[T] {
  def c(t: T): Int
}

object HasC {
  implicit object AHasC extends HasC[A] {
    def c(a: A): Int = a.getCFromA
  }
  implicit object BHasC extends HasC[B] {
    def c(b: B): Int = b.getCFromB
  }
} 

def readC[T : HasC](t: T): Int = implicitly[HasC[T]].c(t)

val a = new A()
val b  = new B()

readC(a)
readC(b)

这一行

def readC[T : HasC](t: T): Int = implicitly[HasC[T]].c(t)

只是一种(可以说)更好的写作方式

def readC[T](t: T)(implicit hasC: HasC[T]): Int = hasC.c(t)

请注意,这是更通用的方法,因为我从不要求在 A 和 B 上定义 getC,这就是我将方法重命名为 getCFromA 和 getCFromB 的原因。

类型类是 Scala 中许多函数式编程的基础,类似的概念在其他现代语言中也可用,例如 Haskell、Rust(特征)或 Swift(协议)。

【讨论】:

  • 感谢您的解释和提出的Type类模式,我会更多地研究性能因素。
  • @AbhijitSingh Typeclasses 将比 Structural typing 快几个数量级,reflection 除了不安全之外,还很慢.此外,JIT 将内联大部分间接,这是 reflection 无法做到的。
猜你喜欢
  • 2021-03-26
  • 2017-11-01
  • 2020-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-24
  • 1970-01-01
相关资源
最近更新 更多