在没有继承层次结构的scala中编写通用代码

时间:2014-09-24 22:59:59

标签: scala generics

我有几个类不是从任何超类派生的。他们都定义了大量相同的方法。例如,

class A {
    def getMsgNum = 1
}

class B {
    def getMsgNum = 2
}

我想编写一个泛型函数,它将根据调用的对象函数返回消息num。等等,

def getMsgNum[T](t: T) = t.getMsgNum

我认为,由于类型擦除,我不能指望它可以工作,但我正在查看与ClassTag绑定的视图绑定和上下文,但仍然无效。

def getType[T: ClassTag](msg: T) = {
    msg.getMsgNum
}

我来自C ++背景,我正在尝试为每种类型实现模板编译的效果。

谢谢你的时间!

4 个答案:

答案 0 :(得分:5)

我个人更喜欢adhoc多态与TypeClass(http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html)模式。我认为对于这类问题,它将是更加“真正的scala方式”解决方案。结构化输入在运行时也更昂贵,因为它使用反射进行字段访问。

  class A
  class B

  trait ToMsgNum[T] {
    def getMsgNum: Int
  }

  implicit object AToMsgNum extends ToMsgNum[A] {
    def getMsgNum = 1
  }

  implicit object BToMsgNum extends ToMsgNum[B] {
    def getMsgNum = 2
  }


  def getMsgNum[T: ToMsgNum](t: T) =
    implicitly[ToMsgNum[T]].getMsgNum

  println(getMsgNum(new A))
  println(getMsgNum(new B))

答案 1 :(得分:4)

def getMsgNum[T](t: T)(implicit ev: T => { def getMsgNum: Int }) = t.getMsgNum

其中{ def getMsgNum: Int }是结构类型。来自documentation

  

结构类型是Parent {Decls}形式的一种类型,其中Decls包含不覆盖Parents中任何成员的新成员的声明。

  

结构类型提供了极大的灵活性,因为它们无需先验地定义继承层次结构

请注意,上述解决方案使用隐式反射调用来访问结构类型的字段,这是一种必须通过添加导入显式启用的语言功能

import scala.language.reflectiveCalls

答案 2 :(得分:3)

这与Eugene的解决方案没有什么不同,但我认为它更清晰一点:

// predefined classes you have no access to
class Foo { def someMethod = "foo" }
class Bar { def someMethod = "bar" }
除了反射或结构类型(伪装反射)之外,Scala中没有办法在这些类型上一般调用someMethod可以工作的方式是通过定义知道如何单独处理每种类型的适配器对象,然后对它们进行泛型调用:

trait HasSomeMethod[T] { def someMethod(x: T): String }
object FooHasSomeMethod extends HasSomeMethod[Foo] { def someMethod(x: Foo) = x.someMethod }
object BarHasSomeMethod extends HasSomeMethod[Bar] { def someMethod(x: Bar) = x.someMethod }

现在您可以将其中一个适配器对象传递到需要对Foo#someMethodBar#someMethod进行通用访问的方法中:

def invokeSomeMethod[T](x: T)(adapter: HasSomeMethod[T]) =
  adapter.someMethod(x)

invokeSomeMethod(new Foo)(FooHasSomeMethod)  // returns "foo"
invokeSomeMethod(new Bar)(BarHasSomeMethod)  // returns "bar"

(我们可以在这里使用单个参数列表,但稍后我们将会列出2个列表)

然而,这显然没有我们想要的那么有用,因为我们必须手动传入适配器。让我们介绍一下implicits,让Scala自动查找正确的适配器对象并将其传递给我们的通用但无继承的方法:

implicit object FooHasSomeMethod extends HasSomeMethod[Foo] { ... }
implicit object BarHasSomeMethod extends HasSomeMethod[Bar] { ... }

def invokeSomeMethod[T](x: T)(implicit adapter: HasSomeMethod[T]) =
  adapter.someMethod(x)

现在这些工作:

invokeSomeMethod(new Foo)  // returns "foo"
invokeSomeMethod(new Bar)  // returns "bar"

以上2个电话会自动转换为之前版本中较长的电话; Scala会自动从隐式对象(以及implicit adapterval)中查找def参数的合适值,这在调用的“环境”中是可用的。 / p>


您也可以像这样定义invokeSomeMethod,这只是上述定义的语法糖:

def invokeSomeMethod[T: HasSomeMethod](x: T) =
  implicitly[HasSomeMethod[T]].someMethod(x)

或者,由于T: HasSomeMethod自动生成第二个参数列表implicit evidence$1: HasSomeMethod[T],这也有效:

def invokeSomeMethod[T: HasSomeMethod](x: T) =
  evidence$1.someMethod(x)

上述“模式”称为Type Classes。因此,例如,T: HasSomeMethod位可以读作“属于类型类T的某些类型HasSomeMethod”(或“......已成为类型类的实例{ {1}}“)。

有关类型类的详细信息,请参阅例如http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html


您还可以为甚至没有HasSomeMethod的类定义HasSomeMethod类型类实例,也不会与someMethodFoo有任何其他相似之处,如果需要的话:

Bar

如果您需要为许多类定义该类型类的实例,您可以拥有一个帮助程序(名称与类型类匹配,以获得良好性):

implicit object IntHasSomeMethod extends HasSomeMethod[Int] {
  def someMethod(x: Int) = "this is an int: " + x
}

invokeSomeMethod(3)  // returns "this is an int: 3"

现在您可以非常简洁地定义类型类实例(适配器):

def HasSomeMethod[T](fn: T => String) = new HasSomeMethod[T] {
  def someMethod(x: T) = fn(x)
}

答案 3 :(得分:0)

如果你不想使用结构类型(反射)和隐式,那么如何在它上面创建适配器,所以你自己的方法getMsgNum将基于适配器而不是已经存在的类来实现。

class A {
  def getMsgNum = 1
}

class B {
  def getMsgNum = 2
}

class C {
  def getMsgNum = 3
}

trait Adaptor[T] {
  def getMsgNum: Int
}

class AdaptorA(t: A) extends Adaptor[A] {
  def getMsgNum = t.getMsgNum
}

class AdaptorB(t: B) extends Adaptor[B] {
  def getMsgNum = t.getMsgNum
}

class AdaptorC(t: C) extends Adaptor[C] {
  def getMsgNum = t.getMsgNum
}


def getMsgNum[T](t: Adaptor[T]) = t.getMsgNum

getMsgNum(new AdaptorA(new A))  //1
getMsgNum(new AdaptorB(new B))  //2
getMsgNum(new AdaptorC(new C))  //3