#480 With-blocks, Constructors, Builders, continued...

brian Fri 6 Mar 2009

I've been doing a lot of immutability work lately, so I've been thinking a lot about the whole constructor problem. I know recently we have been talking about first class with-blocks or first class builders. But I keep coming back this sort of example:

Frame
{
  title = "Hello"
  Label { text = "label" }
  pack
}

We have three things going on here:

  1. setting fields
  2. adding a child (implicit add)
  3. calling a method

All of the above tasks are against an implied variable which happens to be the result of the base expression (in this case the result of the constructor). Our ctor solution requires packaging up all three of those things to pass to the constructor so that they can be executed lazily, then checked:

class Frame 
{
  new make(With x)
  {
    x.apply(this)
    errorChecking
  }
}

Looking at the above, I always come back around to the fact that I really want a with-block to just be a closure. Closures capture the exact execution I want: field sets, adds, and arbitrary method calls into bytecode I can efficiently execute lazily.

So I would like to come back around to the really great discussion from last July on unification of with-blocks and closures. I think that discussion got hung up on the non-local return issue. So for strawman sake, let's just say a with-block can't use a return statement.

If we can past that, then the primary thing missing from a closure to achieve with-block syntax is John's notion of bindscope. If we apply some of the ideas from that discussion to a constructor example:

class Employee
{
  new make(|bindscope This| with) 
  {
    with.callOn(this)
    if (id <= 0) throw ArgErr("id must be > 0")
  }

  const Int id
}

Employee |Employee e| { e.id = 1234 }  // could do this
Employee { id = 1234 }  // really want to do this

Packaging up with-blocks as a closure with some sort of bindscope seems like it can potentially solve most all the use cases without introducing any new major concepts into Fan. Although what I don't like about bindscope is all its ambiguity. True this is like bindscope, but it has zero ambiguity because there are only two scopes: locals and this. Your locals are explicitly defined right there in the block of code. However with multiple potential class scopes, adding new slots can change the meaning of the code - that is bad.

So one thought is to define a bindscope mechanism (or Groovy like it) which works like with-blocks today: it implicitly binds LHS always to the bindscope, but binds everything else according to standard local/this rules.

So I'm thinking instead of continuing the first class with-block or builder train of thought, I'd like to revisit making with-blocks closures and trying to figure out this bindscope issue.

freddy33 Fri 6 Mar 2009

I personally already played with this idea of "viral closures". I wanted something like Obj::execute(|,| virus) where the top scope of virus execution is the called class.

Really powerful feature, that can help breaking OO encapsulation when necessary.

Now in your case, doing it on constructor only to avoid the builder pattern, sounds very interesting.

Questions are:

  1. Do you want to explicitly show which constructors can have this kind of with block ?
  2. Do you want to differentiate with blocks ? The one with bindscope, the one without ?
  3. How do you access Caller.this ?

I think 1 and 2 answer is no.

Still, there is one pattern I don't see in this thinking is the usage of some init closure for a once getter. Having easy closure injection for getters solves some of my with block constructors issues. The only way I managed to do it in Fan is not very elegant, I'll start another thread about it.

JohnDG Mon 9 Mar 2009

One advantage of the builder approach is that you get to inspect the whole changeset and make sure the changes together make sense, before applying it to the current object. This lets you guarantee that objects exist only in valid states, because you would never apply an invalid changeset to an object.

In the closure approach, since the closure actually changes the object, it's entirely possible that objects are left in invalid states. Where this typically manifests itself is in server code that accepts incoming commands, and catches any exceptions that arise when executing the commands (this is a very common pattern). With the builder approach, you have built-in, lightweight transactions.

The builder approach also unifies updates to mutable objects with "updates" to immutable objects, whereas the closure solution cannot be generalized.

Finally, the builder approach lets you take the same builder and apply it to many different objects, which is useful for composite objects, and it gives you an easy way to implement the visitor and observer design patterns.

Now, let's ignore all of the above and assume that closures are the way to go.

Honestly, I think it makes the most sense to allow first-class method closures. That is, closure functions specified with some special syntax that marks them as anonymous methods of a class.

I'm not sure exactly what this would look like, but if I could create an anonymous method for class Foo, then it would behave exactly the same as a method already defined in Foo, and would therefore inherit an implicit this parameter.

The with method (:) could then accept such a method and invoke it. A compiler-supplied default constructor could do the same.

For Void returning closures that accept no arguments, it makes sense to allow the user to omit extraneous syntax. So:

Employee |,| { this.id = 1234 }

becomes:

Employee { id = 1234 }

Note that in the first example, some special syntax is needed to denote that |,| is actually a method closure and not a function closure.

As for what all this would look like, I'm not sure. Perhaps steal an idea from Objective C and use -:

-|,| foo // method closure

But I think it's intuitively appealing to unify methods and with blocks because everyone understands what a method is and a with block is trying to do much of the same stuff, albeit from outside the class. Hence, anonymous external method.

it implicitly binds LHS always to the bindscope, but binds everything else according to standard local/this rules.

I've never liked this, except in the limited case of named parameters (because it's understood that's how they work). Why? Because it's another rule to memorize and it's completely foreign to how closures work. It doesn't play well with local variables, either, and if the with blocks are true closures than you should be able to do anything in them that you can do in ordinary closures.

brian Mon 9 Mar 2009

One advantage of the builder approach is that you get to inspect the whole changeset and make sure the changes together make sense, before applying it to the current object. This lets you guarantee that objects exist only in valid states, because you would never apply an invalid changeset to an object.

I agree you get to inspect the whole changeset, but I disagree that it doesn't let you keep your objects in a consistent state. You pass the closure to the constructor, apply it, then verify the final state before allowing the constructor to exit. But if you are talking about applying a closure to a mutable object outside of the constructor, then yes I agree it is a little less flexible - but that isn't much different then verifying code just calling field setters individually.

The builder approach also unifies updates to mutable objects with "updates" to immutable objects, whereas the closure solution cannot be generalized.

I don't see the distinction - a closure which packages up a series of field sets can be utilized to do updates on both mutable and immutable objects (depending on how the class is designed). To me the only difference is the reflection capabilities.

Finally, the builder approach lets you take the same builder and apply it to many different objects

Again I don't see why a closure can't be used to do this also. For example to apply the x and y setter to a list of mutable points (using today's syntax):

f := |Point pt| { pt.x = 6; pt.y = 7 }
points.each(f)

Honestly, I think it makes the most sense to allow first-class method closures. That is, closure functions specified with some special syntax that marks them as anonymous methods of a class.

This is a really interesting idea - that might be a really nice way to solve the implicit scoping issue.

Another idea I had was that even if we merge multiple scopes, I actually always know the exact slots available in each scope and force a compile time error if there is any ambiguity.

But before we go down either of those paths we have to decide to give up this:

x := 8
y := 9
Point { x = x; y = y }

I do tend to use that a lot, but it only works when there is different rules regarding how LHS and RHS is bound to scope (how with-blocks work today). Although certainly you can make the case that having different rules for LHS and RHS is confusing and inconsistent (but does come in really handy).

I need to think more about the method closure - first off, I think we need some more intuitive names than method closure vs function closure. But I think the idea of a special type of closure with method like scoping could really work well.

To summarize what I think your proposal is:

  1. Method closures have an implicit this based on first arg
  2. Function closures continue to work like they do today
  3. Method closures are declared like -|Foo self| { ...}
  4. Or method closures can use implicit self of base expr: foo {...}

brian Tue 10 Mar 2009

After sleeping it, I think the most natural syntax is to just use the this keyword:

|Point this| { x = 0;  y = 0 }

tompalmer Fri 13 Mar 2009

Although current with blocks and this scope behave differently?

brian Sat 14 Mar 2009

Although current with blocks and this scope behave differently?

Yes, with blocks only bind to target on the LHS of assignments. This scope works just like Java regardless of LHS or RHS.

tompalmer Sat 14 Mar 2009

Okay. Sorry for not paying close enough attention. For conflicts, do items in the new this win, or do local scope vars win? If this overrides outer local vars, then I think it introduces more of the versioning complaints I've had with implicit adds, or with blocks in most languages. But that's been discussed previously.

JohnDG Wed 18 Mar 2009

I agree you get to inspect the whole changeset, but I disagree that it doesn't let you keep your objects in a consistent state.

Look at this code:

e := Ellipse { xrad = 10, yrad = 10 }

try {
    e : { xrad = 10, yrad = -10 }
}
catch (Err e) {
}

// do something with e

With closures, e is a valid instance but exists in an invalid state, since it's y radius has been set to -10. With builders, e is a valid instance and exists in a valid state, which is identically equal to its state prior to the failed with block.

Builders provide transactioning, which is something that closures cannot provide.

I don't see the distinction - a closure which packages up a series of field sets can be utilized to do updates on both mutable and immutable objects (depending on how the class is designed).

I don't see how, but perhaps you have some machinery in mind that would allow the following code to work for both mutable and immutable objects:

pt = pt : { x = 1 }

1. Method closures have an implicit this based on first arg 2. Function closures continue to work like they do today 3. Method closures are declared like -|Foo self| { ...} 4. Or method closures can use implicit self of base expr: foo {...}

That's pretty much my proposal for the case that builders are rejected in favor of closures (I'm still not sure closures are a better fit, perhaps it will be clearer after a more comprehensive proposal).

|Point this| { x = 0; y = 0 }

I agree this is a better syntax.

Any closure whose first argument is X this is an anonymous method closure for the class X and all subclasses. Such anonymous method closures can be invoked as if they were methods on any object whose type is X or a subclass of X.

c := |Point this| { x = 0; y = 0 }

c(pt)

Logically, an anonymous method closure would have access to all fields, but this might be undesirable for security or performance reasons.

qualidafial Wed 18 Mar 2009

Is there a way to refer to the target object directly inside a with-block?

I'm trying to write a quick SWT program in Fan, however SWT widgets require the parent widget as the first parameter.

using [java] org.eclipse.swt
using [java] org.eclipse.swt.widgets
using [java] org.eclipse.swt.events
using [java] org.eclipse.swt.layout

class SwtTest : Test
{
  Void main()
  {
    display := Display()
    shell := Shell(display) {
      setLayout(FillLayout())
      Label(this, SWT.NONE) {
        setText("hello world")
      }
    }

    shell.open
    while (!shell.isDisposed)
      if (!display.readAndDispatch)
        display.sleep

    display.dispose
  }
}

Using this scopes to the enclosing class SwtTest. I've also tried with, self, and several combinations.

brian Wed 18 Mar 2009

@john

I have been giving this issue a lot more thought. I am still really thinking that turning with-blocks into closures is the best solution for the language. I am now thinking more along the lines of a Groovy style "it" versus "this", and detecting ambiguities at compile time. I'm going to give it some more thought, then I will post a more comprehensive proposal. Nailing down this problem is our key issue.

@qualidafial

Right now there is no way to get access to the implicit target of a with-block. So you can't write SWT code using with-blocks very well.

Although take a look at the FWT - these are nice high level Fan APIs for building UIs which use SWT under the covers.

Login or Signup to reply.