简体   繁体   中英

In Scala, How to perform compile-time type check on companion object?

An easy thing to do in many languages but not in Scala is:

Define archetype 'Super', such that all implementations of 'Super' has to define a constructor 'create()'.

I found this constraint very important and is able to identify a lot of problems before runtime. However this feature is only partially enforced in Java (by defining an 'abstract' static method that always throws an error) and completely missing in Scala (companion object is completely detached from class and cannot be enforced in archetype).

is there a macro or tool that allows me to do this?

UPDATE Sorry my question was missing context and examples. Here is a formal use case in scala:

In project A, we define an interface that can be extended by all subprojects:

trait AbstractFoo {}

This interface should always have a default 0-parameter builder/constructor, so project A can initialize it on-demand, however, the implementation of each constructor is unknown to project A:

object AbstractFoo {

  def default[T <: AbstractFoo: ClassTag](): T
}

So the problem becomes: How to rigorously define AbstractFoo, such that for all subprojects of A, any implementation(s) of AbstractFoo:

case class Foo(...) extends AbstractFoo

must satisfy:

  1. 'Foo' must have a 0-parameter builder/constructor defined (presumably in its companion object)

  2. calling AbstractFoo.defaultFoo can invoke this 0-parameter builder/constructor

It should be noted that in an alternative conditions, a solution exists which is to define every companion object as an implicit type class:

trait FooBuilder[T <: AbstractFoo] {
  def default(): T
}

object AbstractFoo {

  implicit object Foo extends FooBuilder[Foo] {
    def default() = {...}
  }

  def default[T <: AbstractFoo: FooBuilder](): T = {
    implicitly[FooBuilder[T]].default
  }
}

Such that if the implicit object is undefined the compiler will give an implicit not found error (my code snippet may have some syntax error, the idea is from http://www.cakesolutions.net/teamblogs/demystifying-implicits-and-typeclasses-in-scala )

Unfortunately it's not always convenient, because this subproject of A is usually unknown to project A. Yet the default implicit builder cannot be redefined, this makes every invocation of default() more covoluted.

I believe scala is a very extendable language, so there should be at least 1 way to enforce it whether if using macro, annotation or other metaprogramming techniques. Is my question clear enough now?

UPDATE2: I believe I found the solution after carefully study Scaladoc, there is a comment hidden in a corner:

if there are several eligible arguments which match the implicit parameter's type, a most specific one will be chosen using the rules of static overloading resolution (see Scala Specification §6.26.4):

...

Implicit scope of type arguments (2.8.0)

...

So all I need is to write an implicit function in FooBuilder:

trait FooBuilder[T <: AbstractFoo] {
  def default(): T

  implicit def self = this
}

object Foo extends FooBuilder[Foo]

So everytime someone call:

default[Foo]

scala will refer to the scope of class Foo, which include object Foo, which contains the implicit value Foo, and eventually find the 0-parameter constructor.

I think this definition is better than defining it under object FooBuilder, since you can only define FooBuilder once, thus its not quite extendable. Would you agree with me? If so, could you please revise your answer so I can award you point?

I don't understand why an abstract class or even a Trait won't allow this to be done?

abstract class DefineCreate{
  def create(): Unit
}

case class Foo(one: Int)
object Foo extends DefineCreate{
  def create(): Unit =  { Console.out.println("side-effect") }
}

Thus I force a user to make a create method on the object in question because all implementations of DefineCreate must do so in order to compile.

Update Following Comments

Well, without having to resort to macros and the like, you could achieve the same sort of thing with type classes:

trait Constructor[A]{
  def create(): A
}

object Construct{
  def create[A](implicit cr: Constructor[A]): A = cr.create()
}

Which doesn't explicitly force the companion object to sprout methods but it does force a user to make the type class if they want to use the Constructor.create[Foo] pattern.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM