You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@groovy.apache.org by Saravanan Palanichamy <ch...@gmail.com> on 2023/08/27 12:59:01 UTC

Generics question

Hello folks

I am building an AST transformation that will allow me to define a concept
of mutable and immutable objects. Java lets me hold mutable objects in
immutable object lists (as per the code below). But my DSL also wants to
allow immutable objects to be held in mutable lists. The meaning it is
conveying is that adding to mutable lists will clone the immutable object
and not affect the original ( I use the << operator to denote this. It
denotes that the object is being streamed into the list and will not affect
the object if list contents are modified) . What is the best way to achieve
this? I dont see a type checking extension that will catch this missing
"method" and let me override it.

I dont want to create an extension function, because this could be a model
I want to follow for several classes and not just Blah. Which is why I want
to use the AST route to solve this problem. Any pointers will be very
helpful.

Assume that I can convert immutable to mutable behind the scenes. Also
assume that 100s of classes will follow this model of mutability

static class Blah {
> static class Immutable {
> String getProperty1() { "PROP1" }
> }
> static class Mutable extends Immutable {
> void setProperty1(String value) {
> }
> }
> }
> void fn() {
> // This works
> List<Blah.Immutable> myOtherThings = []
> myOtherThings << new Blah.Immutable()
> myOtherThings << new Blah.Mutable()
>
> // This does not (I want an AST that will make this work)
> List<Blah.Mutable> myThings = []
> myThings << new Blah.Immutable()
>
> // This works
> myThings << new Blah.Mutable()
> }
>

regards
Saravanan

Re: Generics question

Posted by Jochen Theodorou <bl...@gmx.org>.
On 28.08.23 02:25, Saravanan Palanichamy wrote:
[...]
I think I understand a bit better now...

[...]
> 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?

I think the substitution for List<Person> should then be
List<Person.Mutable | Person Immutable>. While this cannot be expressed
in source we have UnionTypeClassNode for this in the AST. Though that
node is mainly for a different context and could cause compiler problems.

bye Jochen


Re: Generics question

Posted by Saravanan Palanichamy <ch...@gmail.com>.
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

Re: Generics question

Posted by Jochen Theodorou <bl...@gmx.org>.
On 27.08.23 16:36, Saravanan Palanichamy wrote:
> Jochen,
>
> Thank you for your quick response.
>
> I did consider this, but I want to be able to use a mutable where-ever
> immutable is allowed. For example, if a function can take all immutable
> parameters, I want to be able to call it with mutable types as well. So
> my choices are either bypass typing when I try to use mutable where
> immutables are needed (in addition to dealing with the generics issue I
> called out) or just deal with the generics issue I am looking to solve.
> Either way, I'll have to do something (or am I thinking too much :D)
>
> I specifically want this to work, because my DSL is trying to implement
> ReadOnly constructs so Mutable/Immutable are not really types seen by
> the programmer, its just used during the type checking mechanism to make
> sure something marked @ReadOnly is not modified

for me they are kind of exclusive, but ok.

> So the programmer will type
>
> void fn1(@ReadOnly Entity myInputParameter) {
>      List<Entity> things = []
>      things << myInputParameter
>
>      // This should cause a compile error
>      myInputParameter.myProperty = "1234
> }

that should be possible without much problem. A bit more tricky is

def x = myInputParameter
x.myProperty = "1234"

but still possible I would say. Where it gets really problematic is then

things[0].myProperty = "1234"

> and I want to see
>
> void fn1(Entity.Immutable myInputParameter) {
>      // I want this to work but it wont because of groovy type checking
>      List<Entity.Mutable> things = []
>      things << myInputParameter
>
>      // This will automatically fail (like I want it to) because
> setMyProperty is not a method in immutable and is only available in mutable
>      myInputParameter.myProperty = "1234"
> }

but what, except that you replaced the annotations with two types is now
really different?

But why not extend the annotation idea?

void fn1(@ReadOnly Entity myInputParameter) {
      List<@ReadOnly Entity> things = []
      // only allow if T(myInputParameter) is
      // @ReadOnly Entity
      things << myInputParameter

      // fails because of @ReadOnly
      myInputParameter.myProperty = "1234
      // fails because of propagated @ReadOnly
      things[0].myProperty = "1234"
      // could be allowed or forbidden based
      // on @Readonly
      Entity x = things[0]
      things[0].myProperty = "1234"
}


Frankly I currently don't see how to solve this based on the type system
if you do not want to do it like I suggested.

In general adding restrictions or making something found that is not
found otherwise is easy with the type checking extensions. But to tell
the compiler that something is found but wrong or that something should
be propagated in a different way than usual... that is difficult with
the current infrastructure. One way to solve this would then imho be to
find a version that passes compilation and let that the typechecker
check that instead. And then add your checks for immutability.

bye Jochen





Re: Generics question

Posted by Saravanan Palanichamy <ch...@gmail.com>.
Jochen,

Thank you for your quick response.

I did consider this, but I want to be able to use a mutable where-ever
immutable is allowed. For example, if a function can take all immutable
parameters, I want to be able to call it with mutable types as well. So my
choices are either bypass typing when I try to use mutable where immutables
are needed (in addition to dealing with the generics issue I called out) or
just deal with the generics issue I am looking to solve. Either way, I'll
have to do something (or am I thinking too much :D)

I specifically want this to work, because my DSL is trying to implement
ReadOnly constructs so Mutable/Immutable are not really types seen by the
programmer, its just used during the type checking mechanism to make sure
something marked @ReadOnly is not modified

So the programmer will type

void fn1(@ReadOnly Entity myInputParameter) {
    List<Entity> things = []
    things << myInputParameter

    // This should cause a compile error
    myInputParameter.myProperty = "1234
}

and I want to see

void fn1(Entity.Immutable myInputParameter) {
    // I want this to work but it wont because of groovy type checking
    List<Entity.Mutable> things = []
    things << myInputParameter

    // This will automatically fail (like I want it to) because
setMyProperty is not a method in immutable and is only available in mutable
    myInputParameter.myProperty = "1234"
}

// Now both these will compile fine
fn1(new Entity.Mutable())
fn1(new Entity.Immutable())

It does look like I am bypassing the type system, but only in certain cases
specifically generics

regards
Saravanan

On Sun, Aug 27, 2023 at 6:58 PM Jochen Theodorou <bl...@gmx.org> wrote:

> hi,
>
> Basically you want to use an AST transform to bypass the type system? I
> don't have enough data, but I wonder if you cannot solve the problem
> without this.
>
> I mean... why does Mutable extend Immutable? How about...
>
> interface Entity {}
> class Mutable implement Entity [}
> class Immutable implements Entity {}
>
> if you use sealed, then those two classes can be designated as the only
> implementations as well. Though I assume that is not what you intend.
>
> Then you should be able to do /I have tested nothing):
>
> List<? extends Entity> myOtherThings = []
> myOtherThings << new Immutable()
> myOtherThings << new Mutable()
>
> List<Immutale> myThings = []
> myThings << new Immutable()
> // myThings << new Mutable() forbidden
>
> List<Mutable> myMutables = []
> // myMutables << new Immutable() forbiddden
> myMutables << new Mutable()
>
> On 27.08.23 14:59, Saravanan Palanichamy wrote:
> > Hello folks
> >
> > I am building an AST transformation that will allow me to define a
> > concept of mutable and immutable objects. Java lets me hold mutable
> > objects in immutable object lists (as per the code below). But my DSL
> > also wants to allow immutable objects to be held in mutable lists. The
> > meaning it is conveying is that adding to mutable lists will clone the
> > immutable object and not affect the original ( I use the << operator to
> > denote this. It denotes that the object is being streamed into the list
> > and will not affect the object if list contents are modified) . What is
> > the best way to achieve this? I dont see a type checking extension that
> > will catch this missing "method" and let me override it.
> >
> > I dont want to create an extension function, because this could be a
> > model I want to follow for several classes and not just Blah. Which is
> > why I want to use the AST route to solve this problem. Any pointers will
> > be very helpful.
> >
> > Assume that I can convert immutable to mutable behind the scenes. Also
> > assume that 100s of classes will follow this model of mutability
> >
> >     static class Blah {
> >     static class Immutable {
> >     String getProperty1() { "PROP1" }
> >     }
> >     static class Mutable extends Immutable {
> >     void setProperty1(String value) {
> >     }
> >     }
> >     }
> >     void fn() {
> >     // This works
> >     List<Blah.Immutable> myOtherThings = []
> >     myOtherThings << new Blah.Immutable()
> >     myOtherThings << new Blah.Mutable()
> >
> >     // This does not (I want an AST that will make this work)
> >     List<Blah.Mutable> myThings = []
> >     myThings << new Blah.Immutable()
> >
> >     // This works
> >     myThings << new Blah.Mutable()
> >     }
> >
> >
> > regards
> > Saravanan
>
>

Re: Generics question

Posted by Jochen Theodorou <bl...@gmx.org>.
hi,

Basically you want to use an AST transform to bypass the type system? I
don't have enough data, but I wonder if you cannot solve the problem
without this.

I mean... why does Mutable extend Immutable? How about...

interface Entity {}
class Mutable implement Entity [}
class Immutable implements Entity {}

if you use sealed, then those two classes can be designated as the only
implementations as well. Though I assume that is not what you intend.

Then you should be able to do /I have tested nothing):

List<? extends Entity> myOtherThings = []
myOtherThings << new Immutable()
myOtherThings << new Mutable()

List<Immutale> myThings = []
myThings << new Immutable()
// myThings << new Mutable() forbidden

List<Mutable> myMutables = []
// myMutables << new Immutable() forbiddden
myMutables << new Mutable()

On 27.08.23 14:59, Saravanan Palanichamy wrote:
> Hello folks
>
> I am building an AST transformation that will allow me to define a
> concept of mutable and immutable objects. Java lets me hold mutable
> objects in immutable object lists (as per the code below). But my DSL
> also wants to allow immutable objects to be held in mutable lists. The
> meaning it is conveying is that adding to mutable lists will clone the
> immutable object and not affect the original ( I use the << operator to
> denote this. It denotes that the object is being streamed into the list
> and will not affect the object if list contents are modified) . What is
> the best way to achieve this? I dont see a type checking extension that
> will catch this missing "method" and let me override it.
>
> I dont want to create an extension function, because this could be a
> model I want to follow for several classes and not just Blah. Which is
> why I want to use the AST route to solve this problem. Any pointers will
> be very helpful.
>
> Assume that I can convert immutable to mutable behind the scenes. Also
> assume that 100s of classes will follow this model of mutability
>
>     static class Blah {
>     static class Immutable {
>     String getProperty1() { "PROP1" }
>     }
>     static class Mutable extends Immutable {
>     void setProperty1(String value) {
>     }
>     }
>     }
>     void fn() {
>     // This works
>     List<Blah.Immutable> myOtherThings = []
>     myOtherThings << new Blah.Immutable()
>     myOtherThings << new Blah.Mutable()
>
>     // This does not (I want an AST that will make this work)
>     List<Blah.Mutable> myThings = []
>     myThings << new Blah.Immutable()
>
>     // This works
>     myThings << new Blah.Mutable()
>     }
>
>
> regards
> Saravanan