Extending a generic type

I need to redefine a behaviour for a specific trait that is mixed in in many different implementations.

trait Something {
  def doStuff: Result
}

implementations

class Concrete1 extends Something { 
  def concrete1Method1...
  def concrete1Method2...
}

class Concrete2 extends Something { ... }
...
class ConcreteN extends Something { ... }

EDIT: I have only the instances available so I can’t modify the code or the way this objects are created.

I was thinking to subclass each Concrete extending Concrete again

class Concrete1Sub(val underlying Concrete1) extends Concrete1 {
  // forward calls
  override def concrete1Method1 = underlying.concrete1Method1
  ...
 
  override def doStuff: Result = // some new code
}


That would be extremely tedious!

Is there any way to do this more concisely and preferably with no macros?
something

class Composed[T <: Something](val underlying T) extends T {
  // what here?
}

Specifically for the forward calls, you can do:

class Concrete1Sub(val underlying Concrete1) extends Concrete1 {
  export underlying.{concrete1Method1, ...}
 
  override def doStuff: Result = // some new code
}

For the rest I don’t really get what you are trying to do, so I’m afraid I cannot help

1 Like

thanks, I forgot to mention that I’m looking for a scala 2 solution here.

but yes the idea is to have a generic way to export any method from a generic T

I’m just catching up with Export Clauses

seems this is the feature I was looking for… for scala2

If you really need to override that specific method there’s not much else that you can do. Besides maybe hope that someone wrote some kind of “automatic proxy” library, but that will involve macros. Or use dynamic proxy classes, but that involves Java reflection, and only seems to work for interfaces… You even got lucky that those classes are not defined as final, since you have no control over those classes or objects.

Do concrete1Method1, concrete1Method2, etc, depend on doStuff? If so, I don’t see any great solutions, I’m afraid.

(My usual response to this sort of “I need to add new behavior but don’t control the class” is to use a type class, but if you don’t control the call sites that use the underlying method that probably won’t work.)

Some of the subclasses do depend on it. What is the suggestion in case there is no dependency?

type class is not an option I don’t control who builds the classes and I don’t control the end consumers, I just have a small scope where the instance is passed down along with other dependencies.

case class Instances(...,c1: Concrete1, ...)

def someWiring: Instances = ???

def modify(f: Instances): Instances = // I can intercept and change here 

def logic(i: Instances) = ???

logic(
 modify(someWiring)
)

Somebody did - Raphael Winterhalter wrote ByteBuddy. No macros required.

You can see it in action here: curium/src/main/scala/com/sageserpent/curium/ImmutableObjectStorageImplementation.scala at 51ef4586a0663e21db6acf8634d2f6bae356ad87 · sageserpent-open/curium · GitHub.

The job there is to define proxies to incrementally deserialised Scala objects; additional methods and state are added into the proxy, with delegation of inherited traits to the deserialised object.

ByteBuddy will let you override specific methods and do a custom implementation, with or without forwarding to what plays the role of super. You can also use it to just reimplement your own subclass at runtime and instantiate that - so no proxies at all; that might work if you can supply the instances, even though the traits and classes are third party.

EDIT: for the sake of completeness, it is possible to use ByteBuddy to patch existing compiled classes, but to do that requires tapping into the Java agent mechanism. I’ve done this myself in extremis, but don’t recommend it.

2 Likes

Ok but personally I think I’d still prefer macros over runtime class instrumentation :sweat_smile:
But it does seem like it could solve the problem here. IIUC you can use it as a more powerful version of the Java dynamic proxy mechanism.

1 Like

Yeah, I was thinking type classes. But I agree that, from the sound of things, that’s not an option. So yeah, I’m not seeing any good options, just the stuff that’s been explored above.