I have a library I'm building where I have a particular class in which I would like to make a part of the class customizable. The user would be able to pass in a block of code so they can modify the contents as they see fit (within some bounds of course). This is really to keep from having a user to extends a different class implementation for small differences.
I originally was thinking I could do a pass-by-name, but the issue is I can't reference i2
in this case (which makes sense to me).
class MyClass[T](i: Int)(body: => T) {
var i2 = i
val something = body
}
val myc = new MyClass(1)(
//Do some work on i2 here
println(s"i2") //Compile error, i2 not found
)
Is there a way to do something like this where I can reference the internal class variables/methods during the custom body
definition? I've been searching and I believe I'm in the "not sure what question to ask" realm here.
CodePudding user response:
What's wrong with inheritance?
class MyClass[T](s: T){
var i2 = s
}
val myc = new MyClass("You should not use mutable vars") {
i2 = "Because this is not how scala is intended to be used!"
i2 = "Providing library methods allowing users mutate internal structures"
i2 = " is especially bad!"
i2 = " You really, really, really, really should NOT be doing this!"
println(s"$i2")
}
Alternatively, you can pass a the class instance as a parameter to the function:
class MyClass[T](s: T)(body: MyClass[T] => Unit) {
var i2 = s
val something = body(this)
}
val myC = new MyClass("This approach") { x =>
x.i2 = " isn't really any better then the other one."
x.i2 = " If the user wants a different value for i2, "
x.i2 = " then why don't they pass it in to begin with?"
x.i2 = " what is the point in passing one value"
x.i2 = " and a function that replaces it with another?"
x.i2 = " Mutable vars are evil."
x.i2 = " Do not use them!"
println(x.i2)
}
CodePudding user response:
You could try making the by-name parameter a function from MyClass[T] => T
and then call it using body(this)
. Be careful if doing this around initialization order within the class. I forget whether it would be able to access non-public members of MyClass
, though that probably isn't a problem when using this for interacting with defined extension points.
class MyClass[T](i: Int)(body: MyClass[T] => T) {
var i2 = i
val something = body(this)
}
The type inference is a little nutty, but:
scala> val myc = new MyClass[Unit](1)(x => println(s"${x.i2}"))
1
val myc: MyClass[Unit] = MyClass@3f875466
scala> myc.something
something
of course, in this example is the Unit
value ()
, which sbt console suppresses. By having body
be a higher-order function, one can then inject behavior:
scala> val otherMyc = new MyClass[Int => Unit](1)(x => inc => x.i2 = inc)
val otherMyc: MyClass[Int => Unit] = MyClass@2ec032f4
scala> otherMyc.i2
val res4: Int = 1
scala> otherMyc.something(2)
scala> otherMyc.i2
val res6: Int = 3
It occurs to me that you might be able to use a class that can only have instances constructed in MyClass
(e.g. a class defined in the companion object with limited constructor visibility) and pass that as a second argument to body
(i.e. make body
a (MyClass[T], MyClass.PrivateEvidence) => T
). Then you can make methods that would otherwise be private be effectively private by having them require a MyClass.PrivateEvidence
argument (they could take that further and check that it's that instance's PrivateEvidence
). Of course, there's nothing intrinsically stopping the user-provided body
from leaking the PrivateEvidence
.
You could also define a trait
with what the extensions are allowed to manipulate. MyClass
then constructs an instance of that trait and passes it to body
, as in:
trait MyClassAccess {
def setI2(x: Int): Unit
def i2: Int
}
class MyClass[T](i: Int)(body: MyClassAccess => T) {
private var i2: Int = i
private val mcthis = this
private val access = new MyClassAccess {
def setI2(x: Int): Unit = mcthis.i2 = x
def i2: Int = mcthis.i2
}
val something = body(access)
}
scala> val adder: MyClassAccess => Int => Unit = { mca => increment => mca.setI2(mca.i2 increment); println(s"i2 is ${mca.i2}") }
val adder: MyClassAccess => (Int => Unit) = $Lambda$5580/0x0000000840d16040@218339fd
scala> new MyClass(1)(adder)
val res9: MyClass[Int => Unit] = MyClass@c5adf11
scala> res9.something(1)
i2 is 2
scala> res9.something(2)
i2 is 4