Reputation: 501
Please consider a family of plain and case classes, cross-compiled to JVM and Scala.js, which make mixed use of properties with automatically and manually defined accessor methods. Many come from external libraries but all inherit from a specific trait owned by the enquirer:
trait ContextuallyMutable { ... }
How could one enforce a system-wide constraint on all setter/mutator methods of every class that inherits from ContextuallyMutable?
The constraint in question divides execution contexts into two types: owner and client. If invoked from an owner context, a setter executes normally, but in client contexts it initiates a remote procedure call.
As a crude example, consider a simple case class:
case class PropTest(var i:Int) extends ContextuallyMutable
Which prompts the Scala compiler to generate a setter/mutator method something like:
def i_=(i0:Int):Unit = this._i = i0
To satisfy the constraint, the setter method should instead read something like:
def i_=(i0:Int):Unit = {
if (isOwnerContext) { // prohibit direct mutation in client contexts.
this._i = i0
} else tell(this, i0) // fire and forget message to owner context
}
In a smaller, less collaborative project, one could imagine writing constraint enforcement into every setter/mutator of every class by hand:
case class PropTest(private var _i:Int) extends ContextPropped {
def i:Int = _i
def i_=(i0:Int):Unit = {
if (isOwnerContext) wrapped.i = i0
else tell(this, i0)
}
}
... but one should rather not imagine such tedious, error prone, boilerplate lest one inflict like tedium on the maintainers of contributory libraries.
Besides, manually overridden accessor methods conflict with established serialization specs because of Scala's conventional use of the underscore: private var _propName:Type
for manually defined properties.
{ "i":42 } /* rewards intuition while */ { "_i":42 } // frightens it away
Without altering the source code of a Scala.js class, how could one imbue its setter/mutator methods with alternative behaviors conditioned on invocation context?
In broader terms: Does the Scala.js ecosystem offer any way(s) to augment or replace the auto-generated setter/mutator methods for property fields of scala classes?
Edit: Further research has uncovered a few possibilities and related sub-questions:
case class PropTest(var i:Int): extends ContextPropped
class PropTestWrapper(private val wrapped:PropTest){
def i:Int = wrapped.i
def i_=(i0:Int):Unit = {
if (isOwnerContext) wrapped.i = i0
else tell(this, i0)
}
}
class PropTestSubclass(var _i:Int) extends PropTest(_i) {
// Compiler Error: mutable variable cannot be overridden
override def i_=(i0:Int):Unit = {
if (isOwnerContext) wrapped.i = i0
else tell(this, i0)
}
}
class PropTestProxy(private val wrapped:PropTest) extends PropTest(wrapped.i) {
// Compiler Error: mutable variable cannot be overridden
override def i_=(i0:Int):Unit = {
if (isOwnerContext) wrapped.i = i0
else tell(this, i0)
}
}
Of course, these compiler errors disappear after rewriting the original case class as:
case class PropTest(private var _i:Int) extends ContextPropped {
def i:Int = _i
def i_=(i0:Int):Unit = this._i = i0
}
... but, as alluded to earlier: this intervention requires more typing than a complete rewrite of the entire project from scratch.
Another approach looks more directly to the concept of a Delegate/Proxy instance. The main challenge with proxy classes seems to stem from a sort of type merge problem. An ideal proxy will have no user distinguishable difference from the type it mediates.
In the Scala ecosystem, can a delegate/proxy ever take the place of its target in a fully type safe way? What would cmhteixeira say?
Other compiler plugin solutions:
Thank you for any consideration!
Upvotes: 1
Views: 123