What is an idiomatic way to create a read-only wrapper for a class?


#1

I just have followed a recipe in the SO Java question and created a read-only wrapper RTokenBuilder around TokenBuilder class in my pet project to provide a read-only interface to TokenBuilder which cannot be bypassed using simple type cast.

Do we have a more idiomatic way to create a read-only wrapper for a class in Kotlin? The method suggested in the recipe definitely works but requires a bit of boilerplate code, which would be nice to avoid using some Kotlin magic :slight_smile:


#2

I would suggests using an inline class to avoid the overhead of creating a new object around TokenBuilder.

inline class RTokenBuilder<E: Enum<E>>(private val instance: TokenBuilder<E>) {
    val type: E = instance.type
    val start: Int = instance.start
    val finish: Int = instance.finish
    val status: BuildingStatus = instance.status
}

Note: I didn’t test this code but I think it will work.


#3

Mybe you can Extract an Interface, then using anonymous inner class and the syntax ’ by ’

the Code , see [BuilderFactory.createBuilder()]

interface BuilderStatus

// Extract an Interface
interface Builder<E : Enum<E>> {
    val type: E?
    val start: Int
    val finish: Int
    val status: BuilderStatus?
}

private class TokenBuilder<E : Enum<E>>(
    override var type: E?, //TODO declare using 'var'
    override var start: Int,
    override var finish: Int,
    override var status: BuilderStatus?
) : Builder<E>


class BuilderFactory {

    companion object {

        fun <E : Enum<E>> createBuilder(): Builder<E> = object : Builder<E> by TokenBuilder<E>(
            null,
            0,
            0,
            null
        ) {}
    }

}

#4

Thanks for the prompt reply, @SackCastellon!

Unfortunately, it doesn’t work as expected, see the below screen-shot. But anyway, I’m going to use inline class even with functions instead of fields like in my original version because it seems to be designed exactly for such cases to reduce overhead when we need a wrapper around a class.

It would be nice to have a language-level feature to generate such getters like finish() for all the instance fields without an explicit boilerplate code the same way as we have automated getters for properties, but it seems that we don’t have it currently


#5

Moved this topic to “Language design” category as it seems to be a design question


#6

I like to use delegation, ie:

class ReadOnlyList<E> (val mutableList: MutableList<E>) : List<E> by ymutableList

You may inline the above class.
In such case TokenBuilder have to implements RTokenBuilder interface.


#7

Thanks, @fvasco!

Looks interesting, but with this approach, we still need to write boilerplate code for functions like finish() in TokenBuilder instead of having them in RTokenBuilder. I would prefer to keep this code inside RTokenBuilder as it allows to avoid an extra interface creation and also prevents pollution of TokenBuilder with functions like finish() etc. TokenBuilder already has standard getters for all the public properties, so extra functions look redundant there.


#8

Oh, right I forgot about that restrictions of inline classes, however you can easily overcome it by using custom getters.

inline class RTokenBuilder<E : Enum<E>>(private val instance: TokenBuilder<E>) {
    val type: E get() = instance.type
    val start: Int get() = instance.start
    val finish: Int get() = instance.finish
    val status: BuildingStatus get() = instance.status
}

#9

Thanks again, @SackCastellon!

Yes, I would prefer having properties rather than functions in RTokenBuilder. Your workaround works as expected even with inline class, I’ve just tried.

BTW, it seems I just found a nice Kotlin issue, which I’m going to report separately. It appears regardless of the approach with fields or functions in RTokenBuilder and is related to the duplicate() method I just introduced in RTokenBuilder. With this method and inline class, I’m getting the following nice runtime exception while running my test Application (compilation works fine and IntelliJ IDEA also doesn’t give any warning). With normal class without inline everything works as expected.

Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    com/jvmlab/commons/parse/text/RTokenBuilder.duplicate$default(Lcom/jvmlab/commons/parse/text/RTokenBuilder;Ljava/lang/Enum;IILcom/jvmlab/commons/parse/text/BuildingStatus;ILjava/lang/Object;)Lcom/jvmlab/commons/parse/text/TokenBuilder; @8: invokevirtual
  Reason:
    Type 'com/jvmlab/commons/parse/text/RTokenBuilder' (current frame, stack[0]) is not assignable to 'com/jvmlab/commons/parse/text/TokenBuilder'
  Current Frame:
    bci: @8
    flags: { }
    locals: { 'com/jvmlab/commons/parse/text/RTokenBuilder', 'java/lang/Enum', integer, integer, 'com/jvmlab/commons/parse/text/BuildingStatus', integer, 'java/lang/Object' }
    stack: { 'com/jvmlab/commons/parse/text/RTokenBuilder' }
  Bytecode:
    0000000: 1505 047e 9900 082a b600 0e4c 1505 057e
    0000010: 9900 082a b600 123d 1505 077e 9900 082a
    0000020: b600 153e 1505 1008 7e99 0009 2ab6 0019
    0000030: 3a04 2a2b 1c1d 1904 b800 1db0          
  Stackmap Table:
    same_frame(@12)
    same_frame(@24)
    same_frame(@36)
    same_frame(@50)

	at com.jvmlab.commons.parse.text.TokenBuilder.<init>(TokenBuilder.kt:51)
	at com.jvmlab.commons.parse.text.TokenBuilder.<init>(TokenBuilder.kt:33)
	at com.jvmlab.sandbox.parsetest.ApplicationKt.main(Application.kt:113)

#10

KT-29071 is raised for the above mentioned issue


#11

Have a look to the interfaces MutableList vs List. In plain Java, say, ArrayList implements just the interface List which is actually read-only. In the kotlin-stdlib the JetBrains folks made ArrayList extend MutableList which is an interface with the add() method.

Now MutableList extends list, but you could make the mutable interface not extends the non-mutable and create a wrapper like this:

interface Mutable {
    fun set()
    // whatever
}

interface Static {
    get()
    // whatever
}

interface All: Mutable, Static

class MyActualClass: All {
    lock() = Locked(this)
}

class Locked(private val Static: obj): Static by object

fun main() {
    val myObject = MyActualClass()
    val myLockedObject = myObject.lock()
    // use your locked object
}

Doing like so there is no way you can access the setter methods. Hope it helps :slight_smile:


#12

Dear @lamba92,

Yes, this approach obviously works but still requires some boilerplate code, because you have to list down in the Static interface all the read-only methods you want to expose. Plus the target class has to implement this interface.

One of the key benefits of Kotlin over Java is the elimination of boilerplate code. For example, we have default getter and setter methods and can easily declare properties even within default constructor. The same level of simplicity I would expect here, for example, let’s imagine the following language-level feature:

class SomeClass(val count: Int, var flag: Boolean) {
// some other properties and methods
}

class Wrapper(private val instance) : SomeClass vals by instance

Note vals keyword I have introduced in the Wrapper declaration. With this keyword, I would expect Wrapper to have count and flag pseudo-value-properties which are effectively stored in the instance of Wrapper. All required getters for Wrapper class will be generated automatically.

Kotlin is evolving rapidly, so hopefully, we will have something like that in the future :slight_smile:


#13

Ive been looking hard for quite some time for this magic feature and have not found it. Every seemingly promising approach requires at least two nearly identical classes, if not more. I have found (and implemented) several approaches that achieve this by some form of code generation, but it ‘feels dirty’ –

Essentially the basic problem comes down to the fact that class delegation requires both an interface and an instance (concrete) class, and/or that field delegation require a ‘by delegate’ for each field individually.

Since class level delegation requires an Interface to derive from but a concrete class to delegate to, there is no way using native language features to avoid duplicating every single field somewhere – either field by field using field delegation, or by creating a full concrete class (field by field explicitly).
There is simply no way define a class and its properties once and then say “I want w new class just like THIS one but with all properties “var”” or “I want a concrete class just like this interface with all properties”.

Even delegating properties by a generic ‘Map’ doesnt solve this as you must explicitly enumerate each individual property with a ‘by map’ – you cant do

class derived : base by mapOf()

or any other way to achive the same.

The best I have found is to create a base Interface, then using various tricks like Proxy or code generation (source or bytecode via asm/bytebuddy) create variants of the base class which can then be used for delegation.

For example I have working code that can do this:

interface Base { val a : String, val b: Int … }

val dervied = configure<Base>() { from( "config file" ) }

This doesnt turn a val into a var or visa versa – but does allow one to construct a purely val object from a purely val interface with data taken from elsewhere at runtime.

To turn a val to var or visa versa requires something similar but at compile time instead. There are some frameworks that can do something fairly close which I am modeling a POC on … something like

@CloneVal( package = "val.package" ) 
data class VarClass( var a : String , var b : Int ... ) 

->>
which would produce a new class similar to “VarClass” but using val instead.
Its a bit more work to make them both derive from the same interface …
And yet more work to make an IDE like Eclipse or IntelliJ play nicely.


#14

Yes, there is no way now, but if the Kotlin team implements something like the feature I’ve suggested then it would be quite possible. And it seems to be not very complicated feature, it looks quite similar to automatic getters and setters we already have for Kotlin properties.


#15

On the functional side of Kotlin, the idiomatic way is to just make everything immutable and then everything is read only. If you need to change something you make a new instance (e.g. using data class with all vals which gives you a copy constructor).


#16

You are absolutely right, @dalewking!
But fortunately, Kotlin has other sides also, not only functional :wink: Which is why it still has var keyword :slight_smile:


#17

I understand that, but you asked for the idiomatic way. If you design with immutability, your question is moot.


#18

Even with pure val’s and immutable data classes, kotlin requires some very frustrating code duplication around the incompatibility between data classes, interfaces, and delegation. Class delegation requires interfaces AND implementation classes which by nature cannot be the same class, data classes can inherit from interfaces but must duplicate every single property definition and cannot be inherited from nor serve as the root of a delegation tree (only the leaf). Extending data classes cannot be done by inheritance, only delegation – back to square 1 – requiring yet more complete duplication of the same property declarations leading to a parallel tree of interface hierarchy and data class definition and class delegation. It is a very small confined space of data topology that does not require boilerplate duplication. Adding ‘var’ to the mix makes this more complex - but removing ‘var’ does not make it much less …

That said, its still a hell of a lot better then ‘vanilla java’. I also do not believe its a simple as supposed to ‘fix’ this. Its fairly simple to do an ‘80 % job’ – not so simple to do a 100% perfect job at the language layer.
The #1 aspect of Kotlin (as expression of the language designers philosophies) which I personally value is its ‘practicality’ – the virtue of making hard choices where ‘ideal’ solutions compete with ‘practical’ ones – while at the same time NOT compromising on correctness and efficiency.