Home > OS >  Referencing a class variable during call-by-name parameter
Referencing a class variable during call-by-name parameter

Time:07-16

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
  • Related