Property with different public signature than private

A situation I hit quite frequently that I think has the potential to be prettier is that I often have a property that I want to expose as public with a different type than how I use it as private or protected.

How I do this now:

private val _foo: MutableList<Float> = ArrayList()
val foo: List<Float>
  get() = _foo

It’s not that that code is all that ugly, it’s that this is such a common case where I want to expose a property in an immutable and more general way than how it’s used internally that I have examples of that absolutely everywhere in my codebase and if that were two lines instead of 3 it would make my life 33% happier :slight_smile:

2 Likes

There is a KEEP about this:

2 Likes

Beautiful, thanks!
Reading the KEEP, that’s exactly what I wanted :slight_smile:

Let’s hope it will be part of Kotlin at some time.

1 Like

Have you looked into Delegated Properties? I think you could create either a universal one to cover all similar cases, or create one specifically around lists. I’ll try to remember to try it out when I get time and update this.

Yeah, but that doesn’t change the member type. You could access the delegate and grab the mutable property it wraps, but that wouldn’t really be any cleaner. As far as I know a property delegate can’t split visibility.

Right, you’d still have the private one. The delegated property is on the public one and works with the private one.
That’s the part I have to check whether it works.

like this:

class Redirect<T>(val source: KMutableProperty<out List<T>>){
    operator fun getValue(thisRef: Any?, property: KProperty<*>): List<T> {
        return source.getter.call(thisRef)
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: List<T>) {
        source.setter.call(thisRef, ArrayList<T>(value))
    }
}

class User (list: MutableList<Int>) {
    private var _list = list
    public val outList: List<Int> by Redirect(User::_list)
}

I tried this on try.kotlinlang.org and it compiled fine, but it failed with an exception:

Exception in thread “main” java.lang.IllegalStateException: No BuiltInsLoader implementation was found. Please ensure that the META-INF/services/ is not stripped from your application and that the Java virtual machine is not running under a security manager

which makes it sound like maybe the site doesn’t have the full reflection access. Try it out and let me know. I won’t likely get a chance to try it out properly on IntelliJ until Monday

Here’s a “universal” one, btw:

class Redirect <Private, Public> (
    val source: KMutableProperty<Private>,
    val transUp: (Private) -> Public,
    val transDown: (Public) -> Private) {

    operator fun getValue(thisRef: Any?, property: KProperty<*>): Public {
        return transUp(source.getter.call(thisRef))
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Public) {
        source.setter.call(thisRef, transDown(value))
    }
}

And obviously, you can make a read-only version so that the transDown argument isn’t needed. Or you can make it nullable with a default to null and hope people aren’t dummies.

Sorry, I’m not really following you.
Why would you go through all that trouble of the delegate over just doing this:

class User (private val mutableList: MutableList<Int>) {
    public val list: List<Int> = mutableList
}

The KEEP that wasabi linked was a very succinct discussion of the problem, without such a feature however the next best thing is simply two properties pointing to the same value but with different access.

Except your User class here doesn’t make it possible to make the private property be a var. If you used a var instead of a val and reassigned the private one, it wouldn’t change what the public one returns.

What you’re asking for is a way to define the public property without having to rewrite the getter (and possibly setter) every time. Well, that’s exactly what the Delegated Property does.

Alas, it doesn’t work because reflection didn’t let me get around it being private.

That’s true for the example you’re replying to, but not for the OP’s, because that used get() =, so will always return the current value of _foo. Hmm … I wonder if such getters can be made inline. I’m too lazy / busy to try just now :wink: