Comments on: Series ordinals now behave like PICK
The series ordinals (first, second, etc.) were originally designed to be more strict - mainly for beginner usage. For example:
print first tail "abc"
** Script Error: Out of range or past end
causes an error. However, the pick function does not:
print pick tail "abc" 1
I used to think this difference was ok because experts did not use first. However, I no longer believe that. The ordinals are very efficient functions, and I tend to find myself using them as fast selectors with code like this:
send the-email data the-message data
For high performance code, this method is as fast as you can get. When used in high-speed loops, it is much faster than using object field selectors (those require both path interpretation and run-time selector binding). In fact, I use this method so often that I've implemented an ordain function for defining the above ordinal relationships in my code.
There is only one problem. The "past end" exception is no longer desireable.
In REBOL 3.0 changes in the implementation of the ordinals (from datatype-specific action functions to generic abstract native trampolines) made them by default work like pick (what the trampolines actually call). The past-end error is now gone.
This seems preferable, and the change should be compatible because the past-end usage case was an exception in prior versions, so is not utilized in existing code.
Note that this will require some minor documentation updates.
I use it sometimes to cheaply enforce runtime-errors. Which i get for free, which is good in time-critical code too. But i agree its not obvious. Could there be two versions maybe,
if first? block [ we have something ]
(its bloat, but..)
Related: could the relaxed 'first deal with none, like remove does? for
any[ second? find here that default-value ]|
Will the path notation generate these errors or will they act like pick too?|
Receiving a none for series that are "out of range" seems like an improvement to me.|
How can I distinguish between not there and a none! item?
e.g. I have a block: reduce [a none b]|
One idea: check size.
if 1 >= length? series[ first will work ]
(length works from current position, giving length of the rest
Another idea (sorry i am in extension mood, but i like it):
How to add another value, 'not-found! ? it would work like none, except that series-functions return it.
1) i want
to work, even if out of bounds. Think of lits's supply-block, could be as simple as
supply: [face/text: data/:count/:index]
2) i dont want 'none to work here, because 'none is the value for initialization, "not known yet". if series act on this, a lot init-errors are untrapped.
But fetching from a series is not "not known yet", but "quite precisely known: its not there". only produced by 'find or 'pick . In that case series-access could safely handle it, by returning 'not-found IMHO.
Advantage: cool chaining
either missing? val: third find here that[..][..]
either all[p: find here that third p][..][..]
Disadvantage: yet another feature. Maybe not much advantage, except of beeing cool?
Volker, what happens when you put your not-found! value inside a block?|
When you think about it, series length is just an implementation detail. Series expand when you insert stuff into them, so the length is really just the current length. If you are using none as the value of no value (like NULL in SQL), what really is the difference between returning a none from the middle of a block and returning a none from off the end of one. You could think of a series as being of potentially infinite length, with none as an uninitialized value.|
Gabriele, it returns not-found too, i know. but not-found is a rare value, while none is the most used value in rebol.
Also its main purpose is not to be returned, but to be handled by 'pick & co as a series, instead as an error. So picks can be chained without ugly checks. We could do that with 'none instead,
pick none 24 -> none
but 'none is also the default value in locals and contexts, kind of "not initialized". And i want that to be trapped.
pick not-found 24 -> not-found
would be reasonable safe IMHO.|
Volker, your description of not-found sounds exactly like what none is supposed to mean. That's why none is the most used value in REBOL. Or do you intend not-found to mean the same as unset?|
something in between. I want a none which can not happen accidentally. None is the default for locals and contexts, i can not distingish a none from /local from a none from a failed lookup. So i am in a dilemma:
i want path-lookups (same as chained picks) to succed or not. two-dimensional-arrays, or "third find". i think to split such things with explicit checks is ugly.
But if we change the accessors to simply pass none, instead of throwing an error, thats bad. That would mean
none/5 -> none
a/5 -> none
instead of error. Bad idea, often this is an error.
So lookups need to know i really tried to lookup and it failed.
a: find here that
shall be ok.
And the straightforward way to that is a special none, one which does not occur by auto-initialisation.
(if it is the best one, or if chaining introduces other traps i don't know yet)
Perhaps a /check refinement to pick to do bounds checking, throw an error when it fails?|
What is the most common case for the average script (I know, there is no "average" script)? Do you need to differentiate between a NONE (out of bounds) and a NONE (the value) more often than not, and is there stilll a way to do so (one that isn't too painful, preferably)?
Today, we have the same issue with SELECT, right? SELECT is the way to avoid errors, just like PICK is. If path access still throws errors, you can do it that way or, as Brian suggests, maybe add a refinement to PICK if you *want* an error.
Worst case, you check to see if the index is in bounds, which I think is the less common case.
Having them return NONE is, I think, good for productivity and programming-in-the-small, like associative arrays in AWK.
At the very least, we should remember that none doesn't really mean anything until we decide it does, and that the meaning of "no value" or "missing data" makes as much sense as any.|
Maybe i failed to make it clear: my problem is not with single-level access (although my example looks like this). what i want is this:
> pick pick pick series 1 2 3
should work. even if one of the picks runs out of bounds.|
Volker, would having pick on none return none, and pick/check on none throw an error, work for you?|
Sorry, that was me :)|
That would work. i swap it, the default checks errors, and then:
> pick/from pick/from pick series 1 2 3
> third/from find block key
Would "flow" and still catch that "uninitalized".
(/from was the first word which came to mind)|
No, keep the default as returning none. The /check we are talking about here refers to bounds checking, not error checking. It throws an error, it doesn't check one.
You are assuming that a pick out of bounds is an error - it may not be, depending on your data. I am proposing adding optional bounds checking to pick, for use when you have decided that out-of-bounds access in this case is such an exceptional condition that it is worth throwing an error about. The normal case would be to consider none to be an indicator of missing data, no value, null or whatever.
No, this are two different things, which shall work hand in hand.
One is, 'first does not throw an error, it returns none.
The other is, 'first still throws an error if it *gets* none.
Result: we have relaxed single-dimension-access.
But two dimensions still fail:
> first  ; works now
> first first  ; still error
Why not change that? Its easy:
change 'first to accept none as input,
>first first 
returns none. Problem solved! Cool eh?
A good language catches obious errors.
A common error is a forgotten initialisation.
> lots of code
> a: 
> ;lost of code
> first first a
> lots of code
> ;lost of code
> first first a
See? 'a is none, because 'a is a local!
As usual forgot the initialization..
The current 'first catches that, the smarter one would not.
Bad deal, its a quite common error.
Know imagine a special none, one which does not happen by accident,
but is the result of a failed lookup.
lets call it 'not-found! or 'failed-lookup! or something.
Now the inner 'first would trap forgotten inits,
because it does not allow a real none.
But if only out of range in a series, it would return a 'lookup-failed.
Now the outer 'first would accept 'lookup-failed and simple return it.
Now we can chain lookups happily and look for success in the end.
> if first a [we have more values]
> if first first a [we have more values]
Works with any amount of dimensions.
Now lookup-chains are so usefull that we have even a special syntax for it, pathes.
> first first a
are the same. Wouldn't it be nice if we could write
> if a/1/1 [we have more]
> if all[t: a/1 t/1] [we have more]
Well, with first being a trampoline that calls pick in REBOL 3, you could solve your first problem by having pick on none return none, and fix up the ordinals' argument types to include none.
As for your second problem, your usage example would work just as well with none being the out-of-range value as it would with some other none with a different name. Assuming you solved the first problem, your code could run as-is. I'm still not sold on the need for another none - we have two already, none! and unset! (the erroneous none).
If you are set on bounds checking, that /check refinement could be added to the ordinals too and passed along to pick. When you are debugging you can add the checks where you need them.
"pick as none", that would break proper initialisation-checking. If thats the tradeoff, trash it.|
Post a Comment:
You can post a comment here. Keep it on-topic.