0
votes

I used to be a c++ programmar and new to scala. And I what to do some generic programming in scala such as

   class FooComponent
    {
     public:
       static const int ComponentId = 1;
    }
    class BarComponent
    {
     public:
       static const int ComponentId = 2;
    }
    template<typename T>
    void registerComponent()
    {  
       register(T::ComponentId)
    }

But in scala there is no class static variable. I know there is companion object, but I can not access the companion object by just a type parameter.

What is the correct way to achive similar function in scala?

2
not a c++ programmer, don't you have to say that FooComponent inherits the template?? also when you do T::ComponentId, T does not even have ComponentId?? you can ignore my question obviously I don't know c++ templates :)prayagupd
@prayagupd - A call of the type registerComponent<BarComponent>() will instantiate a function with the body register(BarComponent::ComponentId). T is a stand-in for a type here, not any specific one.StoryTeller - Unslander Monica
@prayagupd You are right about that. It does not know whether T have ComponentId or not. But when you try to instantiate a function with a type T which does not have ComponentId, it will throw a compile errorxdot
that looks interesting, so you can call template which has no definition of ComponentId in it, and it would compile. My guess was template is closer to interface in scala/java/OO, where you predefine what you going to have in your implementation classes.prayagupd
my attempt, not exactly c++ template copy, somewhat closer scastie.scala-lang.org/prayagupd/s5CPeKhsTs2hkg96fwDGKg/1prayagupd

2 Answers

0
votes

You can come close to this using structural typing. But as far as I know you can not combine it with static members from a companion object.

class FooComponent{
  val ComponentId = 1
}

class BarComponent{
  val ComponentId = 2
}

object Main extends App{
  def register(t: {val ComponentId:Int} ) = println(t.ComponentId)

  register(new BarComponent())  // -> 2
  register(new FooComponent())  // -> 1
}

If you need access from both instance and class objects you could do this:

class FooComponent{
  val ComponentId = FooComponent.ComponentId
}
object FooComponent{
  val ComponentId = 1
}

class BarComponent{
  val ComponentId = BarComponent.ComponentId
}
object BarComponent{
  val ComponentId = 2
}

object Main extends App{
  def register(t: {val ComponentId:Int} ) = println(t.ComponentId)

  register(BarComponent)      // -> 2
  register(new BarComponent)  // -> 2
  register(FooComponent)      // -> 1
  register(new FooComponent)  // -> 1

  println(FooComponent.ComponentId)   // -> 1
}
0
votes

If you really wanted to use type parameters only (and be more template-like) you could do it with a macro:

Create the actual macro in a Macro project

import scala.reflect.macros.blackbox

object MyMacroImpl {
 def registerMacro[T: c.WeakTypeTag](c: blackbox.Context): c.universe.Tree = {
  import c.universe._

  val tpe = weakTypeOf[T]
  val companion = tpe.typeSymbol.companion

  q"""MyMacro.register(${companion.name.toTermName}.componentId)"""

}

Use the macro in your project

class FooComponent
object FooComponent {
  val componentId: Int = 1
}

class BarComponent
object BarComponent {
  val componentId: Int = 2
}

object MyMacro {
  def registerComponent[T]: Unit = macro MyMacroImpl.registerMacro[T]
}

object Main {
  def register(id: Int): Unit = println(id)

  MyMacro.registerComponent[FooComponent]
}

Edit: Personally, I would just implement it like this:

def register(id: Int): Unit = println(id)

trait HasComponentId {
  def componentId: Int
}

class FooComponent
object FooComponent extends HasComponentId {
  override val componentId: Int = 1
}

class BarComponent
object BarComponent extends HasComponentId {
  override val componentId: Int = 2
}

def registerComponent(comp: HasComponentId): Unit =
  register(comp.componentId)


assert(registerComponent(FooComponent) == 1)

No macros (and thus much less complexity), no instantiation of objects and the result is pretty much the same.