简体   繁体   English

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

[英]Write generic code in scala without inheritance hierarchy

I have few classes which do not derive from any superclass. 我有几个类不是从任何超类派生的。 They all have bunch of same methods defined. 他们都定义了大量相同的方法。 For example, 例如,

class A {
    def getMsgNum = 1
}

class B {
    def getMsgNum = 2
}

I would like to write a generic function that will return message num based on object function is called with. 我想编写一个泛型函数,它将根据调用的对象函数返回消息num。 So something like, 所以,像,

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

I think that because of type erasure I cannot expect that to work but I was looking at view bound and context bound with ClassTag but that still does not work. 我认为,由于类型擦除,我不能指望它可以工作,但我正在查看与ClassTag绑定的视图绑定和上下文,但仍然无效。

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

I come from C++ background and I am trying to achieve something to the effect of template compilation for every type. 我来自C ++背景,我正在尝试为每种类型实现模板编译的效果。

Thanks for your time! 谢谢你的时间!

I personally prefer adhoc polymorphism with TypeClass ( http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html ) pattern. 我个人更喜欢adhoc多态与TypeClass( http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html )模式。 I think it will be much more "true scala way" solution for this kind of problem. 我认为对于这类问题,它将是更加“真正的scala方式”解决方案。 Also structural typing more expensive at runtime because it use reflection for field access. 结构类型在运行时也更昂贵,因为它使用反射进行字段访问。

  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))
def getMsgNum[T](t: T)(implicit ev: T => { def getMsgNum: Int }) = t.getMsgNum

where { def getMsgNum: Int } is a structural type. 其中{ def getMsgNum: Int }是结构类型。 From the documentation : 文档

A structural type is a type of the form Parents { Decls } where Decls contains declarations of new members that do not override any member in Parents. 结构类型是Parent {Decls}形式的类型,其中Decls包含不覆盖Parents中任何成员的新成员的声明。

and

Structural types provide great flexibility because they avoid the need to define inheritance hierarchies a priori 结构类型提供了极大的灵活性,因为它们无需先验地定义继承层次结构

Please note that the above solution uses an implicit reflective call to access the field of the structural type, a language feature that has to be explicitly enabled by adding the import 请注意,上述解决方案使用隐式反射调用来访问结构类型的字段,这是一种必须通过添加导入显式启用的语言功能

import scala.language.reflectiveCalls

This is not too different from Eugene's solution but I think it's a bit clearer: 这与Eugene的解决方案没有什么不同,但我认为它更清晰一些:

// predefined classes you have no access to
class Foo { def someMethod = "foo" }
class Bar { def someMethod = "bar" }

there's no way in Scala other than reflection or structural types (which is reflection in disguise) to generically call someMethod on these types. 除了反射或结构类型(伪装反射)之外,Scala中没有办法在这些类型上一般调用someMethod The way this can be made to work though, is by defining adapter objects that know how to deal with each type individually, and you then make generic calls on those instead: 可以这样做的方法是,通过定义知道如何单独处理每个类型的适配器对象,然后对它们进行泛型调用:

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 }

now you can pass one of those adapter objects into the method that needs generic access to Foo#someMethod and Bar#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"

(we could have used a single parameter list here but later we'll nede 2 lists anyway) (我们可以在这里使用单个参数列表,但是稍后我们将会列出2个列表)

however, this is obviously not as useful as we'd like as we have to pass in the adapter manually. 然而,这显然没有我们想要的那么有用,因为我们必须手动传递适配器。 Let's introduce implicits to make Scala automatically look up the right adapter object and pass that in to our generic but inheritance'less method: 让我们介绍一下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)

now these work: 现在这些工作:

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

The above 2 calls get translated automatically to the longer calls in the previous version; 以上2个呼叫会自动转换为上一版本中较长的呼叫; Scala looks up suitable values for the implicit adapter parameter automatically from the implicit objects (and also val s and def s, to be precise) available in the "environment" of the call. Scala从调用的“环境”中可用的隐式对象(以及精确的val s和def自动查找implicit adapter参数的合适值。


You can also define invokeSomeMethod like this, which is just syntactic sugar over the above definition: 您也可以像这样定义invokeSomeMethod ,它只是上述定义的语法糖:

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

or, since T: HasSomeMethod auto-generates a second parameter list implicit evidence$1: HasSomeMethod[T] , this also works: 或者,由于T: HasSomeMethod自动生成第二个参数列表implicit evidence$1: HasSomeMethod[T] ,这也有效:

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

The above "pattern" is known as Type Classes . 上述“模式”称为类型类 So for example the T: HasSomeMethod bit can be read as "some type T that belongs to the type class HasSomeMethod " (or "...has been made an instance of the type class HasSomeMethod "). 因此,例如, T: HasSomeMethod位可以被读作“属于类HasSomeMethod某个类型T ”(或“......已经成为类型HasSomeMethod的实例”)。

For more on Type Classes, see eg http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html . 有关类型类的更多信息,请参阅http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html


You can also define the HasSomeMethod type class instance for classes that don't even have someMethod nor bear no other resemblance to Foo and Bar whatsoever, if needed: 您还可以为甚至没有someMethod类定义HasSomeMethod类型类实例,如果需要,也不要与FooBar任何其他相似之处:

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

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

If you need to define an instance of that type class for many classes, you can have a helper (with a name that matches the type class, for niceness): 如果您需要为许多类定义该类型类的实例,您可以拥有一个帮助程序(名称与类型类匹配,以获得良好性):

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

now you can define type class instances (adapters) very concisely: 现在,您可以非常简洁地定义类型类实例(适配器):

implicit val FooHasSomeMethod = HasSomeMethod[Foo](_.someMethod)
implicit val BarHasSomeMethod = HasSomeMethod[Bar](_.someMethod)
implicit val IntHasSomeMethod = HasSomeMethod[Int]("this is an int: " + _)
implicit val PersonHasSomeMethod = HasSomeMethod[Person](_.name)
// etc

If you dont want to use structural type (reflection) and implicit, how about create Adaptor on top of it, so you own method getMsgNum will implement based on the Adaptor instead of already existing class. 如果你不想使用结构类型(反射)和隐式,那么如何在它上面创建Adapter,那么你自己的方法getMsgNum将基于Adapter而不是已经存在的类来实现。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM