AltME groups: search
Help · search scripts · search articles · search mailing listresults summary
world | hits |
r4wp | 17 |
r3wp | 20 |
total: | 37 |
results window for this page: [start: 1 end: 37]
world-name: r4wp
Group: Rebol School ... REBOL School [web-public] | ||
Steeve: 3-Oct-2012 | I don't think you need 'with at all, you can throw back the new parameters as an argument of the throw function, like: >> throw/name reduce args 'recurse | |
Steeve: 3-Oct-2012 | Here is a version with no locals, no temporary context, no shit and not tested ;-) rfunc: func [spec body][ func spec compose/deep [ forever [ set [(spec)] catch/name [ return (func spec body) (spec) ] 'recur ] ] ] recur: func [args][throw/name reduce args 'recur] | |
Ladislav: 3-Oct-2012 | OK, this is the long version: tail-func: func [ { Define a recursive user function with the supplied SPEC and BODY. The function can use a special TAIL-CALL local function to perform a tail-recursive function call. } [catch] spec [block!] {Help string (opt) followed by arg words (and opt type and string)} body [block!] {The body block of the function} /local the-function tail-call context-word ] [ ; define a new 'tail-call local variable tail-call: use [tail-call] ['tail-call] ; bind the given BODY to "know" the 'tail-call variable body: bind/copy body tail-call ; find a local word in SPEC context-word: find spec word! if context-word [context-word: first context-word] ; define the TAIL-CALL function set tail-call func spec compose [ ( either context-word [ ; set parameters to the new arguments compose [set parameters values? (context-word)] ] [[]] ) throw/name none 'tail-call ] ; define the function the-function: throw-on-error [ func spec compose/deep [ (either context-word [context-word] [[]]) while [true] [ catch/name [ return do [(body)] ] 'tail-call ] ] ] if context-word [ ; get the function context context-word: bind? first second :the-function ; replace the context word in the function body by NONE change second :the-function none ; adjust the TAIL-CALL body ; replace the 'parameters word change/only at second get tail-call 2 bind first context-word context-word ] :the-function ] values?: func ['word] [second bind? word] | |
Steeve: 3-Oct-2012 | I think I included all your modifications Ladislav but shortly :-) rfunc: [spec body /local args][ args: to-block form first ( do second func spec compose [bind? (to-lit-word first find spec word!)] ) funct spec compose/deep [ recur: func spec [ throw/name reduce [(args)] 'recur ] forever [ set [(args)] catch/name [ return do [(body)] ] 'recur ] ] ] | |
Steeve: 3-Oct-2012 | Should do the trick: rfunc: [spec body /local args][ args: to-block form first do second func spec compose [bind? (to-lit-word first find spec word!)] funct spec compose/deep [ recur: quote (func spec compose/deep [throw/name reduce [(args)] 'recur]) forever [ set [(args)] catch/name [return do [(body)]] 'recur ] ] ] | |
Steeve: 4-Oct-2012 | Last version. - Any spec accepted but needs at least one parameter (can be just a local) rfunc: func [ [catch] spec [block!] body [block!] /local arg obj recur ][ throw-on-error [ if error? try [arg: to-lit-word first find spec any-word!][ make error! "rfunc needs at least one parameter." ] recur: func spec compose [throw/name bind? (:arg) 'recur] obj: catch/name [do second :recur] 'recur funct spec compose/deep [ recur: quote (:recur) forever [ set/any [(to-block form first obj)] second catch/name [ return do [(body)] ] 'recur ] ] ] ] | |
Steeve: 4-Oct-2012 | Not really fair, you redefine the throw word and expect it to work as is ? Your function got the same problem on my pc, except it occurs at the execution time. ** Script Error: Cannot use path on integer! value ** Where: tail-call ** Near: throw/name none 'tail-cal or maybe you have a new version right now (Actually I have problems with Altme to synchronize with some recent posts I can't see all of them currenly) | |
Steeve: 4-Oct-2012 | Completly changed my mind. It's lot leasier to manage /recur as a refinement! - eg. safe/recur instead of recur - no words collision anymore (obviously /recur can't be used as a parameter). Also really short code rfunc: func [[catch] spec [block!] body [block!] /local ctx fun][ spec: append copy spec /recur ctx: bind? first second fun: throw-on-error [func spec [recur]] change second :fun compose/deep [ if (in ctx 'recur) [throw/name second (ctx) 'recur] while [true][ set/any [(bind to-block form first ctx ctx)] catch/name [ return do [(bind/copy body ctx)] ] 'recur ] ] :fun ] | |
Steeve: 5-Oct-2012 | ;Go back to recur as a function. ;Still recur can't be used as a parameter, local or a refinement. ;This implementation is much more clean (no shitty compose/deep) and still very short. ;The collision of words is avoided by the use of singleton functions #[function!] ;I'm confident with this one. It could be the last one -_-; rfunc: func [[catch] spec [block!] body [block!] /local ctx args][ ctx: bind? first second throw-on-error [ ;* Temporary function created to retrieve parameters ;* and to get a new context for 'recur. ;* The context will remain alive (not GC'ed). func append copy spec /recur [recur] ] args: bind to-block form first ctx ctx ctx/recur: func spec reduce [ quote #[function! ['word] [throw/name second bind? word 'recur]] first args ;* may be 'recur if empty specs (still, it's ok) ] func spec reduce [ quote #[function! [args body][ while [true][set/any args catch/name [return do body] 'recur] ]] head remove back tail args ;* remove 'recur bind/copy body ctx ;* bound 'recur ] ] | |
Steeve: 5-Oct-2012 | ;Go back to recur as a function. ;Still recur can't be used as a parameter, local or a refinement. ;This implementation is much more clean (no shitty compose/deep) and still very short. ;The collision of words is avoided by the use of singleton functions #[function!] ;I'm confident with this one. It could be the last one -_-; rfunc: func [[catch] spec [block!] body [block!] /local ctx args][ ctx: bind? first second throw-on-error [ ;* Temporary function created to retrieve parameters ;* and to get a new context for 'recur. ;* The context will remain alive (not GC'ed). func append copy spec /recur [recur] ] args: bind to-block form first ctx ctx ctx/recur: func spec reduce [ quote #[function! ['word] [throw/name second bind? word 'recur]] first args ;* may be 'recur if empty specs (still, it's ok) ] func spec reduce [ quote #[function! [args body][ while [true][set/any args catch/name [return do body] 'recur] ]] head remove back tail args ;* remove 'recur bind/copy body ctx ;* bound 'recur ] ] | |
Ladislav: 5-Oct-2012 | The fact is that the CATCH/NAME+THROW/NAME pair is not ideal for this, but I do have a function which might be able to handle even the G/RECUR case. | |
Ladislav: 5-Oct-2012 | BTW, Steeve: quote #[function! ['word] [throw/name second bind? word 'recur]] is equivalent to func ['word] [throw/name second bind? word 'recur] | |
Steeve: 5-Oct-2012 | ; Sorry Ladislav I've stolen your idea one should avoid catch/throw interferences ; As an extra (also your idea ?), f/recur and recur are now both allowed. rfunc: func [[catch] spec [block!] body [block!] /local ctx fun][ ctx: bind? take second fun: throw-on-error [ func append copy spec /recur reduce ['recur body] ] insert second :fun reduce [ quote #[function! [[throw] ctx args 'fun body /local ret][ if :ctx/recur [ctx/recur: ctx throw/name second ctx 'recur] ctx/recur: :fun while [true][ set/any 'ret catch/name [return do body] 'recur unless all [value? 'ret block? :ret same? ctx last ret][ throw/name get/any 'ret 'recur ] set/any args ret ] ]] ctx (bind head remove back tail to-block form first ctx ctx) :fun ] :fun ] | |
Steeve: 5-Oct-2012 | ; Ladislav I can't see if you posted new code (WTF Altme) ; So I've tried your idea one should avoid catch/throw interferences ; As an extra (also your idea ?), f/recur and recur are now both allowed. rfunc: func [[catch] spec [block!] body [block!] /local ctx fun][ ctx: bind? take second fun: throw-on-error [ func append copy spec /recur reduce ['recur body] ] insert second :fun reduce [ quote #[function! [[throw] ctx args 'fun body /local ret][ if :ctx/recur [ctx/recur: ctx throw/name second ctx 'recur] ctx/recur: :fun while [true][ set/any 'ret catch/name [return do body] 'recur unless all [value? 'ret block? :ret same? ctx last ret][ throw/name get/any 'ret 'recur ] set/any args ret ] ]] ctx (bind head remove back tail to-block form first ctx ctx) :fun ] :fun ] | |
Steeve: 5-Oct-2012 | ; Ladislav I can't see if you posted new code (WTF Altme) ; So I've tried your idea one should avoid catch/throw interferences ; As an extra (also your idea ?), f/recur and recur are now both allowed. rfunc: func [[catch] spec [block!] body [block!] /local ctx fun][ ctx: bind? take second fun: throw-on-error [ func append copy spec /recur reduce ['recur body] ] insert second :fun reduce [ quote #[function! [[throw] ctx args 'fun body /local ret][ if :ctx/recur [ctx/recur: ctx throw/name second ctx 'recur] ctx/recur: :fun while [true][ set/any 'ret catch/name [return do body] 'recur unless all [value? 'ret block? :ret same? ctx last ret][ throw/name get/any 'ret 'recur ] set/any args ret ] ]] ctx (bind head remove back tail to-block form first ctx ctx) :fun ] :fun ] | |
Ladislav: 5-Oct-2012 | However, Steeve, you probably do not understand what the problem with the f: rfunc [x] [if x = 2 [g/recur 3 5]] code is. The problem in a nutshell is that the G/RECUR call uses G/RECUR calling convention and "expects" the G/RECUR call to be used; however, the CATCH/NAME+THROW/NAME pair does not respect that and actually would do the call of F/RECUR. | |
Steeve: 5-Oct-2012 | This time it's really really my final version T_T - Both f/recur and recur allowed - Catch/throw interferences ok. NB: The code would be simpler in R3 since several workarounds are used to correct misbehaviors of object related natives of R2. Also the lack of the reflexive capability for a function to read its own context in a easy way is definitivly a huge miss. (On can't create anonymous functions without analysing their specs first. What a pain) One would need a reserved word holding the context (like SELF for objects). These shortcomings are making the code too much obfuscated and huge for my taste. I hope it will be corrected in R3.. rfunc: func [ [catch] spec [block!] body [block!] /local ctx args call-tail ][ ctx: bind? first second throw-on-error [ func spec: append copy spec /recur [recur] ] args: bind head remove back tail to-block form first ctx ctx call-tail: func ['word] compose/deep [ set/any [(args)] second bind? word throw/name (ctx) 'recur ] ctx/recur: func spec reduce [:call-tail 'recur] func spec reduce [ quote #[function! [ [throw] 'recur 'call-tail ctx args body /local ret ][ if get/any recur [call-tail :recur] set recur get in ctx 'recur while [true][ set/any 'ret catch/name [return do body] 'recur unless all [value? 'ret same? :ret ctx][ throw/name get/any 'ret 'recur ] set/any args second ctx ] ]] 'recur :call-tail ctx args body ] ] |
world-name: r3wp
Group: Core ... Discuss core issues [web-public] | ||
BrianH: 18-Dec-2008 | Outside of rebcode, you can't goto down, but you can goto up with CATCH/name and THROW/name. Of course the structured goto replacements still work (RETURN, EXIT, BREAK). | |
BrianH: 18-Dec-2008 | continuing: func [code [block!]] [catch/name code 'continue] continue: func [/return value [any-type!]] [throw/name get/any 'value 'continue] | |
Group: !REBOL3-OLD1 ... [web-public] | ||
Volker: 11-May-2006 | ;What do i miss? f: func [ /local g ] [ catch/name [ g: func [n] [ if n = 0 [ return 0 ] if n = 1 [ throw/name 1 'return-from-f ] ] g 0 g 1 g 0 ] 'return-from-f ] probe f | |
Group: !REBOL3 ... [web-public] | ||
Sunanda: 3-Mar-2010 | But exit _is_ an error on its own. It is hard to see why ATTEMPTing an error "is never an error on its own", especially as trying it by itself in a console does create an error. exit ;; REBOL says is an error attempt [exit] ;; Brian and I disagree if this is an error it creates a horribly inconsistent mental model. You could create a trivia/gotcha quiz arond this. Which of these stop with an error? attempt [throw] attempt [throw 0] attempt [throw/break] attempt [throw/break 0] attempt [throw/name] attempt [throw/name 0 0] attempt [throw/name 0 'x] | |
BrianH: 3-Mar-2010 | And the first two THROW/name examples are wrong too, because of argument compatibility. | |
BrianH: 4-Mar-2010 | However, CATCH/name and THROW/name would need the additional memory overhead of a single block of words per task in the dynamic solution to store the currently handled names. | |
BrianH: 4-Mar-2010 | Except the THROW/name block-of-words thing. | |
Gabriele: 5-Mar-2010 | >> do does [loop 1 [catch [attempt [1 / 0]]]] == none >> do does [loop 1 [catch [attempt [exit]]]] >> do does [loop 1 [catch [attempt [return 10]]]] == 10 >> do does [loop 1 [catch [attempt [break]]]] >> do does [loop 1 [catch [attempt [break/return 10]]]] == 10 >> do does [loop 1 [catch [attempt [throw 10]]]] == 10 >> do does [loop 1 [catch [attempt [throw/name 10 'something]]]] == 10 | |
BrianH: 12-Mar-2010 | We use THROW/name to write sandboxed replacements for the unwind functions, non-local dynamic escape functions, etc. But that only works if CATCH doesn't catch it. | |
BrianH: 13-Mar-2010 | The advantage to that is because on those rare occasions when you actually need to catch all THROW and THROW/name unwinds, you most likely need to catch the rest too. You might note that the emergency exceptions - HALT, QUIT/now, errors - aren't proposed to be caught, because the cleanup code would likely interfere with error recovery code or the debugging process. | |
BrianH: 13-Mar-2010 | Most of the time you really don't want THROW/name caught by code that it isn't intended for. When you really want to catch off-topic stuff, you want to catch everything. | |
Group: Core ... Discuss core issues [web-public] | ||
Ladislav: 3-Nov-2010 | you mean for this?: for n 1 5 1 [ catch/name [ if n < 3 [throw/name none 'continue] print n ] 'continue ] | |
Ladislav: 3-Nov-2010 | cc: func [[throw] body [block!]] [catch/name body 'continue] continue: func [[throw]] [throw/name none 'continue] | |
Ladislav: 3-Nov-2010 | A different idea, instantly causing any R2 cycle to "understand continue": cc: func [ {convert a cycle body to "understand" CONTINUE} body [block!] ] [ compose/only [catch/name (body) 'continue] ] continue: func [[throw]] [throw/name none 'continue] ; usage: for n 1 5 1 cc [ if n < 3 [continue] print n ] | |
BrianH: 5-Nov-2010 | You were focusing on localiity of where the code was written, and I was talking of locality in the code that the flow of execution goes through at runtime. For instance, #1744 makes it difficult for non-local-definition code to do man-in-the-middle attacks or spoofing, making it useful for secure mezzanine control flow functions. But #1518 prevents you from being able to pass THROW/name through unknown code at all, making it useless for making mezzanine control flow functions at all. Fixing #1518 is what we do to make #1743 possible, and once #1520 is implemented then the arms race will be over, everything else could be mezzanine or user-defined. | |
Group: !REBOL3 Proposals ... For discussion of feature proposals [web-public] | ||
BrianH: 9-Nov-2010 | There are real learning and semantic advantages to just going with one return model. We just need to make the limitations of whatever model we choose easy for regular programmers to workaround if necessary, and pick the defaults well so the workarounds won't need to be specified as often. The last model satisfies all of those at the expense of losing the benefits of dynamic return, and the next to last doesn't even lose those, though it does lose some simplicity. Given that the remaining benefits of dynamic return can be restored by keeping THROW dynamic and fixing the THROW/name bugs, I'm willing to part with dynamic return and get back the simplicity. | |
Maxim: 11-Nov-2010 | my only desire in all of this discussion is that trhow/catch is kept dynamic and that /name be implemented with context matching and that catch doesn't handle throw/name. | |
Ladislav: 11-Nov-2010 | catch doesn't handle throw/name - you could always use catch/name [...] 'none versus throw/name 'none, if you did not want to catch other throw/name calls, so this is still just "cosmetic", and surely not serious in any sense I can imagine. | |
BrianH: 11-Nov-2010 | 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. | |
BrianH: 11-Nov-2010 | 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. |