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?
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.
Please also note, if you do not want to expose the Bar interface through your Foo class, an alternative approach would be the following
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:
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: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.
Maybe you need a self type?