#492 Obj.with and immutable setters

brian Wed 25 Mar 2009

This is a branch from the general purpose it-block proposal to discuss how mutable and immutable objects provide default clone/change support.

One of the things I proposed is this shortcut:

obj { ... }      // shortcut
obj.with({...})  // long hand for above

Let's assume that we desire this behavior:

  1. On a non-const object, the it-block is applied immediately
  2. On a const object, the it-block is applied to a of clone object

If that is the specified behavior, then maybe we just define the following method on Obj:

This with(|This| itBlock)
{
  if (type.isConst)
  {
    copy := this.clone
    itBlock(copy)
    return copy
  }
  else
  {
    itBlock(this)
    return this
  }
}

The tricky thing is that immutable classes can only have their fields set by their constructor. This means that clone must somehow pass every field set and the itBlock into the class's constructor. I no idea what that looks like.

JohnDG Wed 25 Mar 2009

The tricky thing is that immutable classes can only have their fields set by their constructor. This means that clone must somehow pass every field set and the itBlock into the class's constructor. I no idea what that looks like.

In the builder proposal, it would actually be possible to compute the composite of two builders for the same type, and then apply just the net effect.

With closures/itBlocks, no such solution is possible because every change is immediately enacted on the specified object.

However, there is a possible workaround: construct a mutable clone, apply the changes with an itBlock, and then create an immutable clone. This approach requires substantial reworking, as every immutable object would have to have a corresponding mutable version (even if just hidden behind the scenes).

I suggest something simpler: the fields of a const object needn't be final, and with needn't have a Fan implementation. This frees you to do whatever you want in with even if it doesn't have an implementation in Fan source code.

tompalmer Wed 25 Mar 2009

No particular recommendations, but easily tweaked copies of immutable objects does seem very nice to me.

jodastephen Thu 26 Mar 2009

If the immutable constructor at the bytecode level takes in an instance of the same type, then it can copy all the fields from the object, before calling the it-block:

public final class Immutable {
  private String foo;
  public Immutable(...) { ... }  // public constructor
  private Immutable(Immutable copyFrom, ItBlock b) {
    this.foo = copyFrom.foo;
    v.apply(this);
  }
}

Care would be needed with the Java Memory Model though.

Certainly, the builder approach offered better solutions here. However it suffered from no easy way to call arbitrary code. (Overall, I'm a little worried by the sheer number of classes that it-blocks will actually create)

I'd also like to see a modifier/keyword/annotation that ensures that the value returned from a method is actually used:

Str s = "foo"
s.upper()    // I want this to not compile

This avoids a class of bug. This concept is relevant here, as the immutable with method should force callers to assign (or use in a method call) the result.

brian Thu 26 Mar 2009

When I dig into this, I am going to try and see if I can make const fields final in the JVM bytecode. That will force a solution on most of these issues.

Although at this point, I don't think we have to make those fields final for the JMM. There are really only cases where immutables are shared b/w threads - static fields which are guaranteed safe publication by JMM, and message passing b/w actors which will force a safe publication via synchronization.

I'd also like to see a modifier/keyword/annotation that ensures that the value returned from a method is actually used

Great idea, what would you call it though?

JohnDG Thu 26 Mar 2009

I'd also like to see a modifier/keyword/annotation that ensures that the value returned from a method is actually used:

You could do this automatically for const classes, because an immutable class cannot affect any state change, if a return value is not used, then it's probably a bug.

e.g. Java 101 bug: using String.replace and ignoring the return value.

Although at this point, I don't think we have to make those fields final for the JMM.

No, I don't think you have to, because even thread-local caching will produce safe semantics for fields that do not change post construction, whether or not those fields are declared final, because they won't be cached until another thread accesses them, by which point they are fixed for life.

Well, it seems like everyone is on board with this proposal; only technical details remain to be sorted out.

Re: clone. Perhaps clone could optionally accept an itBlock that would customize the clone details? Then you can have a Fan implementation of with (inlined & optimized by the compiler to immediate execution), and the magic goes inside clone.

jodastephen Thu 26 Mar 2009

I'd also like to see a modifier/keyword/annotation that ensures that the value returned from a method is actually used

Great idea, what would you call it though?

A new keyword?

class Foo {
  assigned Foo clone() {
  }
}

An annotation?

class Foo {
  @Assigned
  Foo clone() {
  }
}

It feels like overide to me, which is why I'd prefer a keyword. Const classes have it implied on all methods returning non-Void.

Overall, I think you should try implementing something based around what we've discussed. I suspect that you'll find some issues as you implement, which may then need more discussion.

Login or Signup to reply.