views:

143

answers:

4

I'm currently discovering scala and I was wondering if I could use traits with a factory.

I tried this :

abstract class Foo {
  ...
}
object Foo {
  def apply() = new Bar

  private class Bar extends Foo {
    ...
  }
}

Foo() with MyTrait // Not working

I guess it's because with must be preceded by new.

So is there any way to do this ?

Thank you

+4  A: 

No it is too late, the instance is already created when the apply() method returns.

What you can do is using the traits inside the factory method. The code below is from a rather big code example I am writing:

object Avatar {
 // Avatar factory method
 def apply(name: String, race: RaceType.Value, character: CharacterType.Value
  ): Avatar = {
    race match {
      case RaceType.Dwarf => {
        character match {
          case CharacterType.Thief => new Avatar(name) with Dwarf with Thief
          case CharacterType.Warrior => new Avatar(name) with Dwarf with Warrior
          case CharacterType.Wizard => new Avatar(name) with Dwarf with Wizard
        }
      }
      case RaceType.Elf => {
        character match {
          case CharacterType.Thief => new Avatar(name) with Elf with Thief
          case CharacterType.Warrior => new Avatar(name) with Elf with Warrior
          case CharacterType.Wizard => new Avatar(name) with Elf with Wizard
        }
      }
    }
  }
}

class Avatar(val name: String) extends Character {
  ...
}

In this code the type (profession and race) of your Avatar is decided in the factory based on the RaceType and CharacterType enumerations. What you have is one factory for all sorts of different types or type combinations.

olle kullberg
In your case we must know in advance the differents traits we could use. But if we don't, is it possible to transfer the traits into the factory method with a definition in the idea of `def apply[T]() = new Foo with T` ?
Mr_Qqn
@Mr_Qqn: No, you can't do this, not even by passing a manifest. If you need this behavior, plan on creating proxies that proxy the particular traits to the object your factory creates, and create (implicit) conversions for those traits that create the necessary proxies.
Ken Bloom
Thanks, it works perfectly with a proxy. But what do you mean by creating implicit conversions ? A `new FooProxy with MyTrait` do the job correctly.
Mr_Qqn
@Ken I would like to see a code example of such a proxy. Could you add a new response to this thread with one example?
olle kullberg
@olle: does http://scaladudes.com/node/15 help?
Aaron Novstrup
@anovstrup I think I understand now. I'm creating a new response myself..
olle kullberg
+3  A: 

Say you have:

class Foo
object Foo { def apply() = new Foo }
trait Baz

Then:

Foo() with Baz

would be analogous to:

val foo = new Foo
foo with Baz

which would imply some kind of prototype-based inheritance, which Scala doesn't have. (As far as I know.)

(I'd guess the error in thinking is intuitively mistaking the = sign for a "substitution sign". I.e. since Foo() means Foo.apply() and which "equals" new Foo, you can substitue Foo() with new Foo. Which obviously you can't.)

Knut Arne Vedaa
+3  A: 

Solution with implicit conversion

Ken suggested that a proxy could help us in this case. What we are trying to do here is to add a trait to the instance after it is created. This "monkey patching" could be useful if someone else wrote the class (and the apply() method) and you cannot access the source. In this case you can do is add a proxy/wrapper on top of the instance by implicit conversion (no manual conversion needed):


Using the Foo example we could do this like this:

class Foo
object Foo { def apply() = new Foo }
trait Baz { def usefulMethod(s: String) = "I am really useful, "+ s }

// ---- Proxy/Wrapper ----
class FooWithBazProxy extends Foo with Baz

// --- Implicit conversion ---
implicit def foo2FooWithBazProxy(foo: Foo): FooWithBazProxy = new FooWithBazProxy

// --- Dummy testcode ---
val foo = Foo()
println(foo.usefulMethod("not!"))

Outputs:

I am really useful, not! 

The reason I do not like this example is:

Baz doesn't use Foo in any way. It is hard to see the reason why we would want to attach the usefulMethod() to Foo.


So I made a new example where the the trait we "monkey patch" into the instance actually uses the instance:

// --------- Predefined types -----------
trait Race {
  def getName: String
}

class Avatar(val name: String) extends Race{
  override def getName = name
}

object Avatar{ 
  def apply() = new Avatar("Xerxes")
}

// ---------- Your new trait -----------
trait Elf extends Race {
  def whoAmI = "I am "+ getName + ", the Elf. "
}

// ---- Proxy/Wrapper ----
class AvatarElfProxy(override val name: String) extends Avatar(name) with Elf

// ---- Implicit conversion ----
implicit def avatar2AvatarElfProxy(Avatar: Avatar): AvatarElfProxy = new AvatarElfProxy(Avatar.name)


// --- Dummy testcode ---
val xerxes= Avatar()
println(xerxes.whoAmI)

Prints:

I am Xerxes, the Elf.

In this example the added Elf trait use the getName method of the instance it extends.

Would be grateful if you see any errors, I am not the good at implicits (yet).

olle kullberg
I extended your solution to enable code like `val xerxes = Avatar[Elf]("Xerxes")`
Aaron Novstrup
+2  A: 

Solution with proxies and implicit conversion

This example extends olle's solution to allow the user to specify the mixin trait when calling the apply() method (e.g. val xerxes = Avatar[Elf]("Xerxes")).

// ----- Predefined types -----

trait Race {
   def whoAmI: String
}

class Avatar[R <: Race](val name: String) 

object Avatar {
   def apply[R <: Race](name: String) = new Avatar[R](name)
}

// ----- Generic proxy -----
class AvatarProxy[R <: Race](val avatar: Avatar[R])

implicit def proxy2Avatar[R <: Race](proxy: AvatarProxy[R]): Avatar[R] = 
      proxy.avatar

// ----- A new trait -----
trait Elf extends Race {
   self: AvatarProxy[Elf] =>
   def whoAmI = "I am " + self.name + ", the Elf."
}

implicit def avatar2Elf(avatar: Avatar[Elf]): AvatarProxy[Elf] with Elf = 
      new AvatarProxy[Elf](avatar) with Elf

// --- Test code -----
val xerxes = Avatar[Elf]("Xerxes")
println(xerxes.whoAmI)

Prints:

I am Xerxes, the Elf.

Aaron Novstrup