Reputation: 643
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
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
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
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