Hello Jochen

Maybe its good for me to state the original problem separately from the
solution I am pursuing to see if there are other ways to do it. Apologies
if the email is too long. I tried to stick to a lot of example rather than
text

The problem. Entities in the DSL are either mutable or immutable. Meaning I
can either just read from them or update their internals. These include
lists, maps etc. For example,

    class Person {
        String name
        Person spouse
        List<String> address
        List<Person> relations
    }

1. I must not modify read only objects
        @ReadOnly Person me = new Person().tap { ... }
        me.name = "MyName" // Fails with name is readonly
        me.address << "Other details" // Fails with list is readonly
        me.relations << new Person() // Fails with list is readonly
        me.relations[0].name = "NEWRELATION" // Fails with name is readonly

2. To modify an object it must be mutable
        Person me = new Person()
        me.name = "MYNAME"
        me.address << "ADDR"

3. A mutable object accessed as read only must not be able to modify content
        @ReadOnly Person safeMe = me
        safeMe.name = "MYNAME" // Fails with name is read only
        me.name = "ICANCHANGETHIS"

4. An immutable list should not allow list and content modification
        @ReadOnly List<Person> persons = [ me, you, him, her]
        persons << everyone // Fails with list is readonly
        persons[0].name = "MyName" // Fails with name is readonly
        def me = persons[0]; me.name = "MyName" // Fails with name is
readonly

5. A mutable list can allow modification of list or content, A mutable list
can accept immutable content but it clones it to make it mutable
        // All of this should work
        List<Person> mutableListOfPersons = [me, you]
        mutableListOfPersons << everyone
        mutableListOfPersons[0].name = "MyName"
        // Watch immutable me being changed because it was cloned into the
list. The original immutableme is still unchanged
        mutableListOfPersons << myImmutableMe
        mutableListOfPersons[1].name = "ChangingTheImmutable"

I will never have a use case where I will need List<@ReadOnly ...> because
either the entire list including its contents is read only or it is
mutable. This explained, I am trying to do this

1. I run a second compile on the code just to check for mutability. These
class files are discarded (so in this compile I am only interested in
semantic validation and not runtime safety. All temporary immutable and
mutable classes generated here are never used in the runtime)
2. During compile, I create Person.Mutable and Person.Immutable. Mutable
derives from immutable.
3. Immutable has only get functions and Mutable has set/get functions (the
overriden get further qualifies the return type to be mutable)
4. I then replace code that uses Person. Everywhere a local variable is
declared, I make it mutable. Everywhere I call a function, I make its
parameters read onlyI then let the Groovy compiler do its magic. This works
perfectly for persons not used inside generic collections

So after compile, my entity looks like this

    class Person {

        interface class Immutable {
            String getName()
            Person.Immutable getSpouse()
            List<String> getAddress()
            List<Person.Immutable> getRelations()
        }

        interface Mutable extends Immutable {

        // All gets with mutable signatures
        String getName()
        Person.Mutable getSpouse()
        List<String> getAddress()
        List<Person.Mutable> getRelations()

        // All sets with immutable signatures to allow both mutable and
immutable
        void setName(String value)
        void setSpouse(Person.Immutable value)
        void setAddress(List<String value)
        void setRelations(List<? extends Person.Immutable> value)
        }
    }

My code then looks like this and works as expected without any major
incidents (newing up the interface is handled through another AST that
replaces constructor call, so ignore that)

1. I must not modify read only objects
        @ReadOnly Person.Immutable me = new Person.Mutable().tap { ... }
        me.name = "MyName" // Fails with name is readonly
        me.address << "Other details" // Fails with list is readonly
        me.relations << new Person() // Fails with list is readonly
        me.relations[0].name = "NEWRELATION" // Fails with name is readonly

2. To modify an object it must be mutable
        Person.Mutable me = new Person.Mutable()
        me.name = "MYNAME"
        me.address << "ADDR"

3. A mutable object accessed as read only must not be able to modify content
        @ReadOnly Person.Immutable safeMe = me // me is mutable
        safeMe.name = "MYNAME" // Fails with name is read only
        me.name = "ICANCHANGETHIS"

4. An immutable list should not allow list and content modification
        @ReadOnly List<Person> persons = [ me, you, him, her]
        persons[0].name = "MyName" // Fails with name is readonly
        def me = persons[0]; me.name = "MyName" // Fails with name is
readonly

5. A mutable list can allow modification of list or content
// All of this should work
        List<Person> mutableListOfPersons = [me, you]
        mutableListOfPersons[0].name = "MyName"

I feel like I can handle the list mutability by further qualifying lists as
MutableList vs ImmutableList and checking after static compile what method
was called on what object (and I can compile error on putAt being used in
ImmutableList etc ...)

The problem I run into however is when I use lists of mutable with
immutables. For example, This entire code should be valid
        void fn(@ReadOnly Person me) {
List<Person> myList = []
myList << me
Map<String, Person> myMap = [:]
myMap << me
}

This gets converted into
void fn(Person.Immutable me) {
List<Person.Mutable> myList = []
myList << me // Fails with me cannot be assigned to mutable list
Map<String, Person.Mutable> myMap = [:]
myMap["ME"] = me // Also fails because types dont match
}

How do I solve this? Are there other issues I am not foreseeing?

regards
Saravanan

Reply via email to