user2887596
user2887596

Reputation: 643

Scala: Creating a trait that forces classes to re-implement certain methods

I am trying to implement a trait that forces each class that extends it (and is not abstract) to implement certain methods (even if they already exist in super-classes). Concretely it should look like this:

trait Debugable {

  override def hashCode(): Int = ???

  override def equals(obj: Any): Boolean = ???

  override def toString: String = ???

}

That is the trait and this the implementation:

class TestClass {

}

object TestClass{
  def main(args: Array[String]): Unit = {
    val t = new TestClass
    println(t)
  }
}

The code above should ideally not compile (since a debugable class does not implement the required methods). In reality this does not only compile, but also throws no run-time exception (it just takes the default implementations of the object class).

Until now nothing managed to generate the expected behaviour. I think macros could help, but I am unsure if macros can express something like:

foreach class
     if class.traits.contains(debugable)
         return class.methods.contains(toString)

I know that I could let some external script do the check and have it bundled with the gradle compile task, but I am hoping for a solution which can be implemented as part of the project itself (since that would make it independent of the build pipeline used and since it should be simpler and easier to maintain/extend than writing a script crawling the entire source code)

Upvotes: 0

Views: 392

Answers (3)

user2887596
user2887596

Reputation: 643

Based upon this I wrote the following, which satisfies my need and does override the default implementations:

trait Debuggable_Helper[T]{
  def hashCode(v: T): Int
  def equals(v: T, b: Any): Boolean
  def toString(v: T): String

}

trait Debuggable[T] extends Debuggable_Helper [Debuggable [T]]{
  override def hashCode(): Int = hashCode(this)
  override def equals(b: Any): Boolean = equals(this, b)
  override def toString(): String = toString(this)

}

class Foo extends Debuggable[Foo]{
  def hashCode(v: Debuggable[Foo]) = 42
  def equals(v: Debuggable[Foo], b: Any) = true
  def toString(v: Debuggable[Foo]) = "woohoo"

}
class Qux extends Foo with Debuggable[Qux] //does not compile


object Test{
  def main(args: Array[String]): Unit = {
    println(new Foo)   // OK - prints 'woohoo'
  }
}

Upvotes: 0

Mario Galic
Mario Galic

Reputation: 48420

This is close to it (and certainly an improvement over what I have), but it does not do exactly what I wanted. If I have a "chain" of classes, then it is enough for the top of the chain to implement the methods.

Typeclass approach can help with that, for example,

trait Debuggable[T] {
  def hashCode(v: T): Int
  def equals(v: T, b: Any): Boolean
  def toString(v: T): String
}

class Foo
class Bar
class Qux extends Foo

object Debuggable {
  implicit val fooDebuggable: Debuggable[Foo] = new Debuggable[Foo] {
    def hashCode(v: Foo) = 42
    def equals(v: Foo, b: Any) = true
    def toString(v: Foo) = "woohoo"
  }
  implicit val barDebuggable: Debuggable[Bar] = new Debuggable[Bar] {
    def hashCode(v: Bar) = 24
    def equals(v: Bar, b: Any) = false
    def toString(v: Bar) = "boohoo"
  }
}

import Debuggable._
def debug[T](v: T)(implicit ev: Debuggable[T]) = ???
debug(new Foo)   // OK
debug(new Bar)   // OK
debug(new Qux)   // Error despite Qux <:< Foo

Upvotes: 3

Scalway
Scalway

Reputation: 1663

In my opinion you should just make it abstract.

// Start writing your ScalaFiddle code here
trait Debugable {
  def debugHashCode:Int
  def debugEquals(obj: Any): Boolean
  def debugToString: String

  override def hashCode(): Int = debugHashCode
  override def equals(obj: Any): Boolean = debugEquals(obj)
  override def toString: String = debugToString
}
//this will not compile
class TestClass extends Debugable { }
//this is OK but you need to implement 3 methods later :)
abstract class TestClass2 extends Debugable {}

https://scalafiddle.io/sf/bym3KFM/0

macros should be last thing you try.

Upvotes: 3

Related Questions