2

I have a trait and some classes extending it. The problem is that all method in the trait should be implemented statically, so I put them in the corresponding companion objects. I wrote something like:

A.scala

trait A

C1.scala

class C1 extends A {
  override def F = C1.F
}
object C1 extends A {
  def F: String = ???
}

...

As the method F has to be implemented twice, I added a corresponding companion trait.

Now I have something like:

A.scala

trait A

AC.scala

trait AC {
 def F: String
}

C1.scala

class C1 extends A {
}
object C1 extends AC {
  def F: String = ???
}

C2.scala

class C2 extends A {
}
object C2 extends AC {
  def F: String = ???
}

...

I feel a little strange because there is nothing but an empty trait A in A.scala. I wonder if there is any better way to organize the codes.

6
  • You do not need A however there is no way to force something to have a companion object and thus no way to force it to extend some trait. - However, IMHO, everytime you need something like this, you can use a Typeclass instead. Commented Nov 27, 2020 at 1:58
  • @jwvh I use AC because if I write class C1 extends A and object C1 extends A I will have to write the implementation of method F twice. Commented Nov 27, 2020 at 2:10
  • Does the empty trait A mark anything? If not, you can simply delete it without any loss. Commented Nov 27, 2020 at 2:20
  • @LuisMiguelMejíaSuárez Thanks for the Typeclass suggestion. I've read it briefly but I'm not sure whether it helps. The background of my question is that trait A defined the set of available input of a not-mentioned function. I am writing the instances of A like C1, but I don't want to extends it twice. Commented Nov 27, 2020 at 2:20
  • 1
    @AndreyTyukin I updated the question, the key problem is how to organize the code when all methods in the trait are static. Commented Nov 27, 2020 at 2:30

1 Answer 1

3

I believe that you should first consider whether you really want any methods in the trait A, or whether you could move it into a typeclass (you have a companion object already anyway). The result will look like ordinary method invocation, but all such invocations will, per definition, go through the typeclass instance.


That being said, if you prefer "extending"...

If there are multiple methods in trait A, and they all should be implemented in the companion object, you could use the following classic OOP-ish pattern from Scala collections:

trait A[C <: A[C]] {
  self: C =>
  def companion: ACompanion[C]           // one abstract `def`
  def f(): String = companion.f()        // implemented once
}

trait ACompanion[C <: A[C]] {
  def f(): String
}

class C1 extends A[C1] {
  val companion = C1                     // one line per class `C_N`
}

object C1 extends ACompanion[C1] {
  def f() = "that's a C1"                // all actual implementation in companion
}

println(new C1().f())

The idea is that in the trait A, the methods that rely on the companion (like f), are delegated to the companion (thus, they are defined once as companion.f()).

In the implementing classes, you only have to specify what the companion is (one line per subclass).

This can reduce amount of boilerplate if there are multiple classes like C1 ... C_N, and more methods like f.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.