Motivation:
(not tightly associated with this topic but states a use case. Skippable)
I'm using Scala.js
to write React code. I now have a form
which contains bunch of input
s, each needs a onChange callback
in its props. See Controlled component.
case class Input(info:String,value:String,onChange:(e:Event)=>Unit)
The problem is: info
which has nothing to do with React
, but has been coupled with it. I would split control logic apart from web contents. So I did:
trait Control[T]{val value:T,val onChange:(e:Event)=>Unit}
case class Input(info:String)
//when need to create a controlled props:
private trait TextInputCtrl extends Control[String]{//..implementation}
new Input("some info") with TextInputCtrl
This only solved half the coupling problem. Because implementation of TextInputCtrl
has to be written inside React component to access state. So does new Input(...)
, unless make TextInputCtrl
public. But if you do, you carry React logic outside.
Well. If trait could be dynamically mixed into instance. I could create an uncontrolled clean Input
instance somewhere else, and pass it into React component. Much more flexible. This even provides leverage like:
Seq(input...).map(_ with XXControl)
So, I did some trying:
The question:
I wanted to use this simple philosophy:
def dynamix(in:Input):Input with Control{
new Input(in.info) with Control{//..implementation}
}
but via macros:
q"""
new $MixinToolSym[$tpeC,$tpeT]{
def dynamix(in: $tpeC): $tpeC with $tpeT = {
new $tpeC (..$constrBody) with $tpeT
}
}
"""
I reached successful compilation. But failed at runtime:
trait MixinTool[C, T] {
def dynamix(in: C): C with T
}
object MixinTool {
implicit def materialize[C, T]: MixinTool[C, T] = macro MacroImpl.impl[C, T]
}
val mt=MixinTool.materialize[Input,Control]
mt.dynamix(inputInstance) //error
It throws:
java.lang.IllegalAccessError:
tried to access field x.x.Input.info from class x.x.X$$anon$3$$anon$2
What's the problem?
This is not a problem about scala App
.
===================================================
Discussion about other ways to reach decoupling goal:
Other approaches: (but probably not the best solutions)
Below both lose self type traits' ability to directly access other fields. And hard to incrementally chain or partially override control logic.
case class PropsContainer(input:Input,control:Control = Control.empty)
or:
case class Input(info:String,value:String="",onChange: Control = Control.empty)
def addControl(input:Input,control:Control):Input{
input.copy(onChange = control)
}