Typing over companion object in Scala

2019-04-25 10:23发布

问题:

I have a class and its companion object which together have some reusable functionality. I have encapsulated the functionality of the companion object into a trait, so now the situation is like

class Foo {
  import Foo._

  def foo: Quux = bar(this)
}

trait Bar {
  def bar(f: Foo): Quux = frobnicate(f)
}

object Foo extends Bar

Since Foo.foo is a reusable method, I would like to put it into its trait.

But I have to find a way to tell the type checker that, although bar is not a method on class Foo, it will be in scope because imported from the companion object. I think I need something like being able to type over the companion object of a class.

Is there something like that?

回答1:

There are multiple ways to model the abstraction you need in Scala. I will first describe the most simple pattern and analyze your problem, and then I will describe the most complex pattern, which is used in Scala collections.

The first thing to notice is that companion objects are the right place to put code you will need to call without having an instance of your class, while the place to factor out helpers which you use in instance methods are traits. Furthermore, the smart scala compiler will generate a single static method for your trait, and link all the classes which will use it to it.

From your code point of view, it is very easy to see how it can be factored out into a trait, and then by using the self-type notation, one can enforce that the trait FooTrait can be mixed only if the trait Bar is mixed as well.

class Foo extends FooTrait with Bar 

  trait FooTrait {
    self:Bar =>
    def foo: Quux = bar(this)
  }

  trait Bar {
    def bar(f: Foo): Quux = frobnicate(f)
  }

Please also note, if you do not want to expose the Bar interface through your Foo class, an alternative approach would be the following

  class Foo extends FooTrait {
    protected val barrer = Foo
  }

  trait FooTrait {
    protected val barrer:Bar
    def foo: Quux = barrer.bar(this)
  }

  trait Bar {
    def bar(f: Foo): Quux = frobnicate(f)
  }

  object Foo extends Bar

This second approach works fine when you take a single class and a single companion objects, but does not scale well when you want to develop a hierarchy of classes where you now there is a companion object available for each of these classes, and you also want to enforce the companion object has certain characteristics with respect to the "companed class".

There is a more complex approach, which is used in Scala collections and which I warmly recommend you not to use unless strictly necessary.

Let's start from GenTraversable:

trait GenTraversable[+A]
extends GenTraversableLike[A, GenTraversable[A]]
   with GenTraversableOnce[A]
   with GenericTraversableTemplate[A, GenTraversable]
{
  def seq: Traversable[A]
  def companion: GenericCompanion[GenTraversable] = GenTraversable
}


object GenTraversable extends GenTraversableFactory[GenTraversable] {
  implicit def canBuildFrom[A] = new GenericCanBuildFrom[A]
  def newBuilder[A] = Traversable.newBuilder
}

As you see, the trait defines a companion object, which provides some basic infrastructure for building new collections of the same type (typically for filtering, mapping, and so on).

By going up in the hierarchy, you can see that the def companion is refined:

trait GenIterable[+A]
extends GenIterableLike[A, GenIterable[A]]
   with GenTraversable[A]
   with GenericTraversableTemplate[A, GenIterable]
{
  def seq: Iterable[A]
  override def companion: GenericCompanion[GenIterable] = GenIterable
}


object GenIterable extends GenTraversableFactory[GenIterable] {
  implicit def canBuildFrom[A] = new GenericCanBuildFrom[A]
  def newBuilder[A] = Iterable.newBuilder
}

If you surf among the classes, you will understand that this mechanism is used to guarantee that for each concrete collection implementation, there is a companion in scope with some kind of properties with respect to the class itself. This is possible because it is legal to refine the return type of a method in a child class.

Nevertheless, in order to work correctly this mechanism requires some manual casts and a lot of generic parameters, as well as generic signatures.

trait GenericTraversableTemplate[+A, +CC[X] <: GenTraversable[X]] extends HasNewBuilder[A, CC[A] @uncheckedVariance] {
 protected[this] def newBuilder: Builder[A, CC[A]] = companion.newBuilder[A]

  /** The generic builder that builds instances of $Coll
   *  at arbitrary element types.
   */
  def genericBuilder[B]: Builder[B, CC[B]] = companion.newBuilder[B]

  private def sequential: TraversableOnce[A] =this.asInstanceOf[GenTraversableOnce[A]].seq
// other code
}


回答2:

Maybe you need a self type?

trait Foo { self: Bar => // This says that anything that will mix in Foo
                         // must mix in Bar too.
  def foo: Quux = bar(this)
}

trait Bar {
  def bar(f: Foo): Quux = frobnicate(f)
}

object Foo extends Foo with Bar