A new thread for a new idea, maybe a bit random, maybe not...
What if with-blocks could be operator-overloaded?
class Line {
const Int startX;
const Int startY;
Int endX;
Int endY;
new make(Int startX := 0, Int startY := 0, Int endX := 0, Int endY := 0) {
this.startX = startX; this.startY = startY; this.endX = endX; this.endY = endY;
}
Void with(WithBlock wb) {
// handle a with-block
}
}
line := Line(0, 10, 40, 50)
// invoke normal-with-block
line {endX = 80; endY = 90}
The call to the normal-with-block would package up its data into a WithBlock object, and invoke the operator overload method with(WithBlock). That method could handle the block however it wants, whether logging, sending events or ignoring fields:
Now what if the idea was extended to constructors?
new make(Int startX := 0, Int startY := 0, Int endX := 0, Int endY := 0 :: WithBlock wb) {
this.startX = startX; this.startY = startY; this.endX = endX; this.endY = endY;
wb.apply()
if (startX == null || startY == null) throw ArgErr.make
}
Now, the constructor is responsible for the whole of the construction process. The WithBlock parameter would be optional in code - if you don't write it then it is automatically applied for you. (Note that I don't know exactly where to put the WithBlock argument - first argument, last argument, outside the argument list, with a separator like ::?)
For the caller, nothing too much changes (assuming the shorthand constructor call change goes ahead). The two current mechanisms work:
line := Line(0, 10, 40, 50) // valid
line := Line {startX = 0; startY = 10; endX = 40; endY = 50} // valid
The mechanism where a with-block follows a constructor only now allows non-const fields to be set in the with-block (ie. its just a normal-with-block)
line := Line(0, 10, 40, 50) {endX = 15; endY = 25} // valid - non-const fields being set
line := Line(0, 10, 40, 50) {startX = 15; startY = 25} // invalid - const fields being set
The solution to the second case is to reconsider what a construction-with-block is:
line := Line {startX = 0; startY = 10; endX = 40; endY = 50}
// is syntax sugar for:
line := Line( {startX = 0; startY = 10; endX = 40; endY = 50} )
Thus, the invalid case can be transformed as follows:
line := Line(0, 10, 40, 50) {startX = 15; startY = 25} // invalid - const fields being set
line := Line(0, 10, 40, 50, {startX = 15; startY = 25}) // valid
In summary, the concept is that construction always consists of one call and one set of arguments (which might include a packaged up WithBlock), its just that there is syntax sugar to allow you to drop the round brackets if the only content is a construction-with-block. Quite simple and easy to explain.
Random idea, or sweet notion - you decide!
brianSat 19 Jul 2008
Your idea leads me to this idea...
... maybe with-blocks are really just closures with an implicit this argument.
If we could unify with-blocks and closures into one general purpose language construct that might be a serious break thru. And then as you suggest we'd just be passing a closure/with-block into the constructor as an argument.
I need to do some hard thinking on that one. But this is definitely a good direction to pursue.
tompalmerSun 20 Jul 2008
If we could unify with-blocks and closures into one general purpose language construct that might be a serious break thru.
Okay. You guys are forcing me to mention how my language June was going to work. I've held off (to avoid distractions), but here goes. And note that I'm not necessarily proposing this for Fan. Just showing my ideas that relate to this exact subject.
One of the problems I've mentioned with "with" blocks in other languages is the namespace ambiguity. Fan mostly avoids this by forcing all assignments and calls to bind to the "with" object. Well, except for the recent "add" feature. That's a potential risk, I think. Also, there's implicit arguments like it in Groovy. Sweet, but I'm afraid it could also get ambiguous for nested blocks. Here's how I was going to do things.
# June (vapor language) here, not Fan
# "with" here is just an API function, not a language feature.
# The '^' indicates an implied first param to the closure, known as 'it'.
# Both 'it' and 'this' can exist simultaneously.
with(findPerson(23)) {^
# Calls or var access beginning with '.' refer to the nearest 'it' or 'this'.
# Whichever is closer in scope is the one it always binds to.
.name = "Dude"
.age = 999
}
# ...
# And here is the definition of "with" (June was going to have simple generics).
def with[Item](item: Item, action: do(Item)) {
# Just call the action with the item.
action(item)
}
Note that the only thing that makes it a "with" block is the use of ^ to make the argument become it. Here's the more verbose version of the initial "with" call:
# June still, same behavior as initial "with" call above.
with(findPerson(23)) do(person) {
person.name = "Dude"
person.age = 999
}
# And more verbose still.
with(findPerson(23), do(person: Person) {
person.name = "Dude"
person.age = 999
})
All of those do the same thing. Now, I'm not sure that the exact style should map to Fan, but I think the concepts relate to the current discussion. For instance, if the the with call happens as a callback within a method, then you get to put code before and after automatically. No need for an external "validation" block. But at the same time, you'd need to explicitly allow "with" blocks from your constructors, and it might be harder to enforce cool const rules like Fan has.
In general, I just think any "with" block should be obvious. Fan's current rules make them obvious, even if I'd prefer "obj.{}" to "obj {}". (And then there'd be nonlocal return questions again.) But Fan is obvious. And the forced binding to the "with" object looks nicer than my prefixed ".dot" syntax.
Anyway, just some rambling.
brianSun 20 Jul 2008
That is pretty interesting (can't wait to see this language when it is real :)
The leading dot is interesting. Although I think you would have to take meaninful whitespace further than we do today in Fan. Otherwise you might parse the first example as:
.name = "Dude".age = 999
tompalmerSun 20 Jul 2008
Right about the end lines. In June (as in Python), the grammar tries to terminate statements at newlines eagerly. If you really want to continue a statement outside normal conventions, you'd have to terminate the line with .... In Python, as in shell scripts, it's \ to continue a statement, but I always hated the ending \. Just too in my face for some reason.
brianMon 21 Jul 2008
I gave this idea some thought - especially the with-block/closure unification. I'm not sure it is practical:
requiring every constructor to deal with a with-block seems more brittle than having a single new handler
it pretty much needs to be some kind of closure so that it can be "called"
it doesn't allow with-blocks to be used easily with general purpose statements (or we have to make two different kinds of with-blocks which I don't like either)
that means the same syntax is generating two very different ASTs
scoping with-block different than closure would be confusing if with-blocks really were Funcs, people would try to use with-blocks as no-arg closures are used today
jodastephenMon 21 Jul 2008
I agree that the closure concept is over the top, although I think you could generate some interesting bytecode to avoid the need to create the closure object in most cases.
The part about this idea I like most is:
line := Line {startX = 0; startY = 10; endX = 40; endY = 50}
// is syntax sugar for:
line := Line( {startX = 0; startY = 10; endX = 40; endY = 50} )
// thus, invalid due to const fields
line := Line(0, 10, 40, 50) {startX = 15; startY = 25}
// could be transformed to valid
line := Line(0, 10, 40, 50, {startX = 15; startY = 25})
I think this is a lot more understandable to a newcomer, and is entirely compatible with your "take 3" proposal.
jodastephen Sat 19 Jul 2008
A new thread for a new idea, maybe a bit random, maybe not...
What if with-blocks could be operator-overloaded?
The call to the normal-with-block would package up its data into a WithBlock object, and invoke the operator overload method
with(WithBlock)
. That method could handle the block however it wants, whether logging, sending events or ignoring fields:Now what if the idea was extended to constructors?
Now, the constructor is responsible for the whole of the construction process. The WithBlock parameter would be optional in code - if you don't write it then it is automatically applied for you. (Note that I don't know exactly where to put the WithBlock argument - first argument, last argument, outside the argument list, with a separator like ::?)
For the caller, nothing too much changes (assuming the shorthand constructor call change goes ahead). The two current mechanisms work:
The mechanism where a with-block follows a constructor only now allows non-const fields to be set in the with-block (ie. its just a normal-with-block)
The solution to the second case is to reconsider what a construction-with-block is:
Thus, the invalid case can be transformed as follows:
In summary, the concept is that construction always consists of one call and one set of arguments (which might include a packaged up WithBlock), its just that there is syntax sugar to allow you to drop the round brackets if the only content is a construction-with-block. Quite simple and easy to explain.
Random idea, or sweet notion - you decide!
brian Sat 19 Jul 2008
Your idea leads me to this idea...
... maybe with-blocks are really just closures with an implicit
this
argument.If we could unify with-blocks and closures into one general purpose language construct that might be a serious break thru. And then as you suggest we'd just be passing a closure/with-block into the constructor as an argument.
I need to do some hard thinking on that one. But this is definitely a good direction to pursue.
tompalmer Sun 20 Jul 2008
Okay. You guys are forcing me to mention how my language June was going to work. I've held off (to avoid distractions), but here goes. And note that I'm not necessarily proposing this for Fan. Just showing my ideas that relate to this exact subject.
One of the problems I've mentioned with "with" blocks in other languages is the namespace ambiguity. Fan mostly avoids this by forcing all assignments and calls to bind to the "with" object. Well, except for the recent "add" feature. That's a potential risk, I think. Also, there's implicit arguments like
it
in Groovy. Sweet, but I'm afraid it could also get ambiguous for nested blocks. Here's how I was going to do things.Note that the only thing that makes it a "with" block is the use of
^
to make the argument becomeit
. Here's the more verbose version of the initial "with" call:All of those do the same thing. Now, I'm not sure that the exact style should map to Fan, but I think the concepts relate to the current discussion. For instance, if the the with call happens as a callback within a method, then you get to put code before and after automatically. No need for an external "validation" block. But at the same time, you'd need to explicitly allow "with" blocks from your constructors, and it might be harder to enforce cool const rules like Fan has.
In general, I just think any "with" block should be obvious. Fan's current rules make them obvious, even if I'd prefer "obj.{}" to "obj {}". (And then there'd be nonlocal return questions again.) But Fan is obvious. And the forced binding to the "with" object looks nicer than my prefixed ".dot" syntax.
Anyway, just some rambling.
brian Sun 20 Jul 2008
That is pretty interesting (can't wait to see this language when it is real :)
The leading dot is interesting. Although I think you would have to take meaninful whitespace further than we do today in Fan. Otherwise you might parse the first example as:
tompalmer Sun 20 Jul 2008
Right about the end lines. In June (as in Python), the grammar tries to terminate statements at newlines eagerly. If you really want to continue a statement outside normal conventions, you'd have to terminate the line with
...
. In Python, as in shell scripts, it's\
to continue a statement, but I always hated the ending\
. Just too in my face for some reason.brian Mon 21 Jul 2008
I gave this idea some thought - especially the with-block/closure unification. I'm not sure it is practical:
jodastephen Mon 21 Jul 2008
I agree that the closure concept is over the top, although I think you could generate some interesting bytecode to avoid the need to create the closure object in most cases.
The part about this idea I like most is:
I think this is a lot more understandable to a newcomer, and is entirely compatible with your "take 3" proposal.