4

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 inputs, 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)
}
Community
  • 1
  • 1
cuz
  • 1,172
  • 11
  • 12

0 Answers0