r3wp [groups: 83 posts: 189283]
  • Home
  • Script library
  • AltME Archive
  • Mailing list
  • Articles Index
  • Site search
 

World: r3wp

[!REBOL3 Proposals] For discussion of feature proposals

Maxim
11-Nov-2010
[244]
btw, the dynamic variable, just solved a long standing binding problem 
riddle I had with classed based instantiation of objects in R2.  
when I had talked to Carl about it at the last devcon, even he couldn't 
explain it... so its a very nice thing to get "out in the open".
Andreas
11-Nov-2010
[245]
With parse being a dialect on it's own, BREAK in PARSE can be separate 
from BREAK in loops. So that's not really an argument to keep dynamic 
BREAK (for loops).
BrianH
11-Nov-2010
[246x3]
That way the issues can be laid out clearly, so the rest of the page 
can just link to the appropriate issues sections instead of repeating 
things.
PARSE doesn't bind the code in its parens - that's regular REBOL. 
Parse also can't know ahead of time which blocks it will be treating 
as rules, because it uses dynamic scope for the whole dialect.
Definitional returns or escapes rely on lexical scope. If you don't 
have lexical scope, you don't have the ability to do definitional. 
So what PARSE needs is for the top-level BREAK to be dynamic. And 
if one level is dynamic, we are better off with all levels being 
dynamic, at least for the same escape function. Same goes for definitional.
Andreas
11-Nov-2010
[249]
Thanks for restating the facts. Once again, there's nothing intrinsic 
in PARSE that forces it to use the same BREAK used in loops.
BrianH
11-Nov-2010
[250]
Yes there is: Definitional breaks redefine BREAK to a definitional 
function, and PARSE relies on BREAK being dynamic because PARSE is 
inherently and necessarily dynamic and doesn't rebind anything in 
the parens, nor should it. For that matter, PARSE rules can be reassigned 
in their productions, so PARSE can't even rely on the rules being 
the same thing the next time round.
Maxim
11-Nov-2010
[251]
note that there are two usable BREAKs in parse.    parse [break] 
  vs   parse [(break)]
Andreas
11-Nov-2010
[252]
Use LEAVE-PARSE instead of BREAK for exiting PARSE.
BrianH
11-Nov-2010
[253]
Maxim, there is also parse [return].
Andreas
11-Nov-2010
[254]
See also the change log notes for A98, where the BREAK handling was 
actually introduced:
http://www.rebol.com/r3/changes.html#section-23
BrianH
11-Nov-2010
[255]
Which is not the same as PARSE [(return)], but PARSE doesn't pay 
attention to the bindings of the keywords in its rules, just those 
of the rule names. And in the productions (parens) PARSE can't do 
any rebinding at all because it can't assume that BREAK is referring 
to the same function.
Andreas
11-Nov-2010
[256x3]
Point is: the behaviour of BREAK in PARSE is not a strong argument 
at all when considering the behaviour of BREAK in loops.
BREAK in PARSE was added as a nice hack, but it certainly is not 
the primary functionality of BREAK.
If it makes sense to change the behaviour regarding the primary functionality 
of BREAK, this cute little hack is the least thing that should stand 
in the way of that change.
BrianH
11-Nov-2010
[259]
The behavior of BREAK in PARSE is the main reason we can get away 
with dropping dynamic return. It's a tradeoff - you get dynamic break 
or dynamic return.
Andreas
11-Nov-2010
[260x2]
Because we can not use dynamic THROW in PARSE, or what?
Dynamic break is completely irrelevant in the context of parse.
BrianH
11-Nov-2010
[262]
Ladislav wants to get rid of dynamic throw as well.
Andreas
11-Nov-2010
[263]
Yes, but that's one argument, not two.
BrianH
11-Nov-2010
[264x2]
It is becoming abundantly clear that there is more and more need 
for a comparison section that shows the strengths of dynamic vs. 
definitional, because people seem to not understand that there are 
ceratin classes of code and algorithms (parsing, for instance) that 
can't be expressed with strict lexical scoping. You are giving up 
a lot when you go definitional, so that better stuff not be as important 
in the context where you do it.
better stuff not be as important -> stuff better not be as important
Andreas
11-Nov-2010
[266x2]
One construct for dynamic scoping is sufficient.
Would we have a WITH-DYNAMIC we wouldn't need anything else.
BrianH
11-Nov-2010
[268x2]
It turns out that with functions, because of the tasking issues, 
you already have to give up the benefits of dynamic return. So getting 
rid of dynamic return is no loss. The same can't be said of BREAK.
It is only because of tasking issues though - otherwise losing dynamic 
return would be a big deal.
Andreas
11-Nov-2010
[270]
To you, personally.
BrianH
11-Nov-2010
[271]
There are some advantages to definitional return as well, so it's 
a net plus.
Andreas
11-Nov-2010
[272]
As should be obvious by now, there are varying views on that issue.
BrianH
11-Nov-2010
[273x3]
Yes, based on varying levels of information. My statement is objective. 
It is all a tradeoff, and I have not at any point tried to hide the 
upsides and downsides of both approaches.
I can assert stuff about PARSE because Peta was nice enough to include 
or link to the mathematical proofs on the Parse Proposals page.
So I can say that PARSE is based on dynamic scope with certainty.
Andreas
11-Nov-2010
[276x2]
No problem with that.
Only that parse does not have any bearing on BREAK for loops.
BrianH
11-Nov-2010
[278x2]
And I can use those same proofs to apply to other algorithms with 
similar characteristics, and *know* that you gain some abilities 
with definitional scope, and lose others. This is why I know that 
Ladislav's DO-ALL is a loop, and so not wanting BREAK to apply to 
it is more of an opinion than something inherent in its nature. But 
that doesn't mean that the need for that is less.
And yes, I would be satisfied with THROW being dynamic and the rest 
not. But my *bare minimum* requirement for accepting that is to fix 
THROW so it actually works properly, and in many ways it doesn't 
at the moment (all with tickets), and in one way it could work better 
(also with a ticket).
Maxim
11-Nov-2010
[280x2]
I've just reread a few things and I now truely understand the deeper 
intricasies of this whole discussion... (finally ;-)
I think that the terms dynamic and definitional aren't making comprehension 
easy, especially dynamic.
BrianH
11-Nov-2010
[282x3]
(phone call)
That is why I prefer lexical instead of definitional. Definitional 
is lexical + faked lexical.
(as terms, not as concepts)
Maxim
11-Nov-2010
[285x4]
also I think that the word unwind from the error document should 
be used, since that is really is what happends afaict.
used in discussion I mean.
I also prefer lexical, though definitional is more *precise*... ironically, 
I didn't understand dynamic return until I grasped what definitional 
return really was.
in my mind they are *both* dynamic returns... you aren't just falling 
of the function's end, removing from the stack.  

you are *causing* that to happen.

the difference is in the how far they unwind...
BrianH
11-Nov-2010
[289]
Please don't take my mentioning of downsides as being a statement 
of opinion or some kind of taking sides. I only mention them because 
they are real, and must be considered when picking a certain strategy. 
Both approaches have plusses and minuses. If you want to make a rational 
choice then you need to know the issues - otherwise you are just 
being a fanboy.


For instance, I picked the definitional side for returns, without 
the need for a fallback to dynamic, because of a rational evaluation 
of the algorithmic style of R3's functions. And it wasn't until I 
remembered that the tasking issues had already removed the advantages 
that dynamic scoping has over lexical scoping - we just can't do 
that stuff as much anymore, so it doesn't matter if we don't try. 
The same goes for loops, but to a lesser extent - loops aren't affected 
as much by tasking issues so we can still do code that would benefit 
from dynamic breaks, but it still might be a worthy tradeoff to avoid 
needing an option (since we have no such option). But for THROW, 
especially THROW/name, there are things that you can do with dynamic 
throw that you *can't* do with definitional, and those things would 
have great value, so it's a rational choice to make the tradeoff 
in favor of dynamic.
Maxim
11-Nov-2010
[290]
can you explain to me how dynamic throw wouldn't be affected by tasking 
when dynamic return would?
BrianH
11-Nov-2010
[291]
Well it comes down to this: Functions are defined lexically. Though 
they are called dynamically, they aren't called until after they 
have already been bound, definitionally. But as a side effect of 
tasking, those bindings are stack-relative, and those stacks are 
task-local. But random blocks of code outside of functions are bound 
to object contexts, and those are *not* task-local. So that means 
that the old R2 practice of calling shared blocks of code is a really 
bad idea in R3 if any words are modified, unless there is some kind 
of locking or synchronization. This means that those blocks need 
to be moved into functions if their code is meant to be sharable, 
which means that at least as far as RETURN and EXIT are concerned, 
they can be considered lexically scoped. The advantage that we would 
get from being able to call a shared block of code and explicitly 
return in that block is moot, because we can't really do that much 
anymore. This means that we don't lose anything by switching to definitional 
code that we haven't already lost for other reasons. At least as 
far as functions are concerned, all task-safe code is definitional.


Loops are also defined lexically, more or less, and the rebinding 
ones are also task-safe because they are BIND/copy'd to a selfless 
object context that is only used for that one call and thrown away 
afterwards. And most calls to loops are task-safe anyways because 
they are contained in functions. However, the LOOP, FORALL, FORSKIP 
and WHILE loops do not rebind at the moment. We actually prefer to 
use those particular loops sometimes in R3 code because they can 
be more efficient than *EACH and REPEAT, because they don't have 
that BIND/copy overhead. Other times we prefer to use *EACH or REPEAT, 
in case particular loop fits better, or has high-enough repetitions 
and enough word references that the 27% overhead for stack-local 
word reference is enough to be more than the once-per-loop BIND/copy 
overhead. Since you don't have to move blocks into *loops* to make 
them task-safe, you can use blocks referred to by word to hold code 
that would be shared between different bits of code in the same function. 
This is called manual common subexpression elimination (CSE), and 
is a common optimization trick in advanced REBOL code, because we 
have to hand-optimize REBOL using tricks that the compiler would 
do for us if we were using a compiled language. Also, PARSE rules 
are often called from loops, and they are frequently (and in specific 
cases necessarily) referred to by word instead of lexically nested; 
most of the time these rules can be quite large, maximizing BIND/copy 
overhead, so you definitely don't want to put the extensive ones 
in a FOREACH or a closure.

Switching to definitional break would have three real downsides:

* Every loop would need to BIND/copy, every time the loop is called, 
including the loops that we were explicitly using because they *don't* 
BIND/copy.

* Code that is not nested in the main loop block would not be able 
to break from that loop. And code that is nested in the main loop 
would BIND/copy.

* We can in theory catch unwinds, run some recovery code, and send 
them on their way (hopefully only in native code, see #1521). Definitional 
escapes might be hard or impossible to catch in this way, depending 
on how they are implemented, and that would mean that you couldn't 
recover from breaks anymore.


The upside to definitional break would be that you could skip past 
a loop or two if you wanted to, something you currently can't do. 
Another way to accomplish that would be to add /name options to all 
the loop functions, and that wouldn't have the BIND/copy overhead. 
Or to use THROW or THROW/name.


The situation with THROW is similar to that of the non-binding loops, 
but more so, still task-safe because of functions. But CATCH and 
THROW are typically the most useful in two scenarios:

* Escaping through a lot of levels that would catch dynamic breaks 
or returns.

* Premade custom escape functions that might need to enforce specific 
semantics.


Both of these uses can cause a great deal of difficulty if we switched 
to definitional throw. In the first case, the code is often either 
broken into different functions (and thus not nested), or all dumped 
into a very large set of nested code that we wouldn't want to BIND/copy. 
Remember, the more levels we want to throw past, the more code that 
goes into implementing those levels. In the second case definitional 
throw would usually not work at all because the CATCH and the THROW 
would contained in different functions, and the code that calls the 
function wrapping the THROW would not be nested inside the CATCH. 
So you would either need to rebind every bit of code that called 
the THROW, or the definitional THROW would need to be passed to the 
code that wants to call it like a continuation (similar concept). 
Either way would be really awkward.


On the plus side of dynamic (whatever), at least it's easy to catch 
an unwind for debugging, testing or recovery purposes. For that matter, 
the main advantage of using THROW/name as the basic operation that 
developers can use to make custom dynamic escape functions is that 
we can build in a standard way to catch it and that will work for 
every custom escape that is built with it. The end to the arms race 
of break-through and catch.
Maxim
11-Nov-2010
[292]
wow... I was wondering why it took you so long to reply  ;-)
BrianH
11-Nov-2010
[293]
Yeah :) And I had another phone call.