Pre-REP on NONE! and SERIES! polymorphism
[1/9] from: joel::neely::fedex::com at: 10-Jun-2002 7:48
Since we've been discussing enhancements recently, let me offer a
common situation (at least in my experience) and ask the collected
wisdom of the list for thoughtful opinions...
There are several REBOL features for working with series values
that are quite comfortable with an empty series:
foreach item foo [print item]
find foo bletch
select foo bletch
Evaluating each of the above with an empty series will nicely
obey the "Principle of Least Surprise" and
do nothing
yield none
yield none
respectively. However, all of the above will choke if FOO is
set to NONE instead. It still seems reasonable in that case for
the expressions to
do nothing
yield none
yield none
IMHO. Given that (for example) PARSE-XML provides NONE instead
of an empty block for the attribute portion of XML elements that
have no attributes, and for the content portion of XML elements
that have not content (and I could give other examples where
compound data structures could have NONE rather than an empty
block due either to simpler initialization or missing data),
I often find myself wrapping FOREACH, FIND, and SELECT with
tests to ensure that the argument is a series instead of a NONE
value, even though I'd be perfectly happy with NONE to behave
exactly the same as an empty series.
So, what to do...?
1) Keep using wrapper code.
2) Define appropriate wrappers and use those wherever needed,
e.g.:
find?: func [s [series! none!] v [any-type!]] [
if found? s [ find s v ]
]
3) Propose a REBOL enhancement...
3a) ...of additional natives FIND? SELECT? and FOREACH?
3b) ...of refinements FIND/ANY SELECT/ANY and FOREACH/ANY
(or /NULL or /EMPTY or whatever...)
3c) ...asking for FIND SELECT and FOREACH to be extended to
accept NONE as a first argument.
Of course, (1) is kludgy but simple on a case-by-case basis,
(2) is straightforward but adds the overhead of function eval
to every use (significant in inner loops, of course), and
(3) is political (and might appear to contradict my POV on
keeping the /Core as focused as possible ;-)
Since the issues here involve BOTH convenience (addressed by all
options but (1)) AND performance (addressed only by (3)), I'm
interested in the views of the list.
OBTW, the above function could certainly have been written
find?: func [s [series! none!] v [any-type!]] [
all [found? s find s v]
]
instead; are there any other interesting variations (just as a
matter of coding style, clever hackage, etc.)?
-jn-
--
; Joel Neely joeldotneelyatfedexdotcom
REBOL [] do [ do func [s] [ foreach [a b] s [prin b] ] sort/skip
do function [s] [t] [ t: "" foreach [a b] s [repend t [b a]] t ] {
| e s m!zauafBpcvekexEohthjJakwLrngohOqrlryRnsctdtiub} 2 ]
[2/9] from: ammon:rcslv at: 8-Jun-2002 15:44
Hi Joel,
I would consider this case a flaw in the REBOL code which leads to the idea
that RT should fix it, but that could take a while depending on how critical
RT considers it to be.... I think that it should be submitted as a bug
report based on the fact that it should take RT a grand total of 5 minutes to
fix it, and it doesn't add much overhead to the size of the interpreter and
it will actually aid in performance (one of the reasons you listed as a good
reason to add it to the Core language)
Just my 2 little pennies
Ammon
A short time ago, Joel Neely, sent an email stating:
[3/9] from: greggirwin:mindspring at: 10-Jun-2002 10:43
Hi Joel,
I've been a big fan of Design by Contract for a while, which leads you to be
very specific and assert everything that's important to ensure reliable
operation. With REBOL, I do things very differently and am still looking at
how that mindset fits with my (evolving) REBOL development style. Without
further digression, I'd vote for 3c.
3c) ...asking for FIND SELECT and FOREACH to be extended to
accept NONE as a first argument.
If returning NONE were always considered to be an error condition, that
would be a different story, but it isn't. From a DbC viewpoint, I could
argue that there is a clear distinction and that you shouldn't be trying to
FIND anything in a none! value but, as you pointed out, returning NONE is a
standard REBOL idiom. If you want to check for NONE values in certain cases,
there is nothing to prevent you from doing that, but that would be the
exception. Most code would benefit I think.
--Gregg
[4/9] from: g:santilli:tiscalinet:it at: 10-Jun-2002 19:39
Hi Joel,
On Monday, June 10, 2002, 2:48:16 PM, you wrote:
JN> Since the issues here involve BOTH convenience (addressed by all
JN> options but (1)) AND performance (addressed only by (3)), I'm
JN> interested in the views of the list.
I'd vote for 2 or maybe 3c.
JN> instead; are there any other interesting variations (just as a
JN> matter of coding style, clever hackage, etc.)?
find any [s []] v
? Or maybe better:
find any [s []] :v
:)
Regards,
Gabriele.
--
Gabriele Santilli <[g--santilli--tiscalinet--it]> -- REBOL Programmer
Amigan -- AGI L'Aquila -- REB: http://web.tiscali.it/rebol/index.r
[5/9] from: lmecir:mbox:vol:cz at: 10-Jun-2002 23:20
Hi all,
I do not hate polymorphism! That said, I must say, that all proposed
solutions look like "an exception to an exception".
1) I do not think, that NONE is an ideal replacement for an empty block.
2) I do not think, that NONE is good at pretending to be a LOGIC! value.
My POV is, that current behaviour is complicated enough and I would prefer
to not complicate things any more. If I had to choose the simplest
variation, I would vote for 3c below, but the simplest way would be to use
empty block where appropriate instead of replacing it by NONE.
----- Original Message -----
From: "Joel Neely"
Since we've been discussing enhancements recently, let me offer a
common situation (at least in my experience) and ask the collected
wisdom of the list for thoughtful opinions...
There are several REBOL features for working with series values
that are quite comfortable with an empty series:
foreach item foo [print item]
find foo bletch
select foo bletch
Evaluating each of the above with an empty series will nicely
obey the "Principle of Least Surprise" and
do nothing
yield none
yield none
respectively. However, all of the above will choke if FOO is
set to NONE instead. It still seems reasonable in that case for
the expressions to
do nothing
yield none
yield none
IMHO. Given that (for example) PARSE-XML provides NONE instead
of an empty block for the attribute portion of XML elements that
have no attributes, and for the content portion of XML elements
that have not content (and I could give other examples where
compound data structures could have NONE rather than an empty
block due either to simpler initialization or missing data),
I often find myself wrapping FOREACH, FIND, and SELECT with
tests to ensure that the argument is a series instead of a NONE
value, even though I'd be perfectly happy with NONE to behave
exactly the same as an empty series.
So, what to do...?
1) Keep using wrapper code.
2) Define appropriate wrappers and use those wherever needed,
e.g.:
find?: func [s [series! none!] v [any-type!]] [
if found? s [ find s v ]
]
3) Propose a REBOL enhancement...
3a) ...of additional natives FIND? SELECT? and FOREACH?
3b) ...of refinements FIND/ANY SELECT/ANY and FOREACH/ANY
(or /NULL or /EMPTY or whatever...)
3c) ...asking for FIND SELECT and FOREACH to be extended to
accept NONE as a first argument.
Of course, (1) is kludgy but simple on a case-by-case basis,
(2) is straightforward but adds the overhead of function eval
to every use (significant in inner loops, of course), and
(3) is political (and might appear to contradict my POV on
keeping the /Core as focused as possible ;-)
Since the issues here involve BOTH convenience (addressed by all
options but (1)) AND performance (addressed only by (3)), I'm
interested in the views of the list.
OBTW, the above function could certainly have been written
find?: func [s [series! none!] v [any-type!]] [
all [found? s find s v]
]
instead; are there any other interesting variations (just as a
matter of coding style, clever hackage, etc.)?
-jn-
--
; Joel Neely joeldotneelyatfedexdotcom
REBOL [] do [ do func [s] [ foreach [a b] s [prin b] ] sort/skip
do function [s] [t] [ t: "" foreach [a b] s [repend t [b a]] t ] {
| e s m!zauafBpcvekexEohthjJakwLrngohOqrlryRnsctdtiub} 2 ]
[6/9] from: joel:neely:fedex at: 10-Jun-2002 19:12
Hi, Gabriele,
Gabriele Santilli wrote:
> find any [s []] v
>
> ? Or maybe better:
>
> find any [s []] :v
>
Outstanding!
-jn-
--
; Joel Neely joeldotneelyatfedexdotcom
REBOL [] do [ do func [s] [ foreach [a b] s [prin b] ] sort/skip
do function [s] [t] [ t: "" foreach [a b] s [repend t [b a]] t ] {
| e s m!zauafBpcvekexEohthjJakwLrngohOqrlryRnsctdtiub} 2 ]
[7/9] from: carl:cybercraft at: 12-Jun-2002 18:50
Hmm. Forgot to hit Send on this yesterday... (:
On 11-Jun-02, Ladislav Mecir wrote:
> Hi all,
> I do not hate polymorphism! That said, I must say, that all proposed
> solutions look like "an exception to an exception".
> 1) I do not think, that NONE is an ideal replacement for an empty
> block.
I agree. My feeling is that if something may return none or an empty
block then there's a difference there for some reason and perhaps you
should be checking for it.
> 2) I do not think, that NONE is good at pretending to be a LOGIC!
> value.
Actually, I like that. I like to be able to use the likes of...
if file-names: request-file [process file-names]
and so on. I wouldn't like an empty block to be treated as false in
such a case though.
--
Carl Read
[8/9] from: joel:neely:fedex at: 12-Jun-2002 8:23
Hi, Ladislav,
Ladislav Mecir wrote:
> Hi all,
>
> I do not hate polymorphism! That said, I must say, that all
> proposed solutions look like "an exception to an exception".
>
Having been accused before of unwillingness to "let REBOL be
REBOL", I'm simply thinking out loud about a way to live with
the current uses of NONE, rather than asking for a change in
that area. (See below.)
> My POV is, that current behaviour is complicated enough and
> I would prefer to not complicate things any more. If I had
> to choose the simplest variation, I would vote for 3c below,
> but the simplest way would be to use empty block where
> appropriate instead of replacing it by NONE.
>
I agree in principle, but in practice I don't see that as
feasible. REBOL currently yields NONE in a variety of cases
to indicate "nothing here", but knowing what was expected by
the author might have to wait until the telepathy module is
implemented!
I'm simply suggesting that taking an action for every piece of
content in "nothing here" and searching for a value in "nothing
here" could be allowed (respectively) to do nothing gracefully
or return "nothing here" gracefully. This has the advantage
that it breaks no existing code at all, and still allows those
who prefer explicit pre-tests to write them.
The goal of this idea-under-discussion is to deal with cases
such as these:
- PARSE-XML provides a three-part element/attributes/content
block for every XML element encountered. However, the
attributes or content portions are NONE, rather than an
empty block, when attributes or content are not present.
Since it is common in XML processing to iterate over the
content of an element, or to search an attribute list for
a given name, it would be nice to have those gracefully
do nothing when handed NONE.
- PARSE-XML's behavior in that case is related to the fact
that PARSE provides NONE instead of empty strings in some
cases. Again, since searching within an empty string for
a substring yields NONE with the desired substring is not
present, it would be convenient to have that null behavior
when the first argument is e.g. a NONE from PARSE rather
than an empty string.
- One can save memory by initializing as-yet-unknown attributes
of an object to NONE, especially when some of them are never
needed for a particular instance. For example, I believe that:
flight-proto: make object! [
passenger-tickets: none
shipment-waybills: none
]
flight101: make flight-proto [
passenger-tickets: [ ... some document IDs ... ]
]
flight407: make flight-proto [
shipment-waybills: [ ... some document IDs ... ]
]
;; and so on for a large number of similar objects
conveyed?: func [flightobj [object!] docID [string!]] [
any [
find flightobj/passenger-tickets docID
find flightobj/shipment-waybills docID
]
]
could save significant memory (depending on population, of
course!) compared with the use of a prototype that initialized
all attributes to empty blocks.
- Depending on the meaning of the data, testing for presence in
a complex data structure could IMHO be clearer with this
behavior in place. As a dinky example, consider a look-up
structure (block-of-block-of-block) containing country,
province/state, city, and address data for the offices of a
fictitious company:
our-offices: [
"US" [
"NY" [
"New York" "101 East 52nd Street"
"Albany" "2541 Washington Blvd"
...
]
"GA" [
"Atlanta" "525 E. Peachtree SW"
...
]
]
"CA" [
"ON" [
...
]
...
]
"CZ" [
...
]
...
]
(my geographic ineptitude and desire to save your time and mine
cause the example to be severely incomplete ;-)
With the behavior under discussion, I can retrieve the address
of an office in a specific city (or the NONE that signals that
there's no such office) with the expression
find find find our-offices country st-province city
which seems much simpler and direct than any expression I can
think of. Gabriele's elegant suggestion with ANY allows
find any [
find any [
find our-offices country
[]
] st-province
[]
] city
(assuming that OUR-OFFICES has been initialized! ;-) which still
takes more work to parse with my poor eyeballs.
While not suggesting that this is the highest priority for RT to
address, I really *do* think that it returns a small measure of
simplicity to existing complexities surrounding NONE, rather than
adding to them, and (given the compatibility with existing code)
has no impact on those who desire not to use the new behavior.
-jn-
--
; Joel Neely joeldotneelyatfedexdotcom
REBOL [] do [ do func [s] [ foreach [a b] s [prin b] ] sort/skip
do function [s] [t] [ t: "" foreach [a b] s [repend t [b a]] t ] {
| e s m!zauafBpcvekexEohthjJakwLrngohOqrlryRnsctdtiub} 2 ]
[9/9] from: lmecir:mbox:vol:cz at: 14-Jun-2002 8:49
Hi all,
this is a thing loosely connected to the subject. I wrote a DEFAULT function
(may be of some use for error handling).
default: function [
{
try to evaluate code
and then evaluate the fault block,
if an error occurs
}
[throw]
code [block!]
fault [block!]
/good pass [block!]
] [result] [
either error? set/any 'result try code [
use [error] compose [
any-type? error: :result
do (reduce [fault])
]
] [
either good [do pass] [get/any 'result]
]
]
Here is a comparison of its behaviour with the functions contained in Core
2.5:
ex1: function [[catch]] [r] [
set/any 'r throw-on-error a
do b
]
ex2: function [[catch]] [r] [
if none? set/any 'r attempt a [
throw make error! "error"
]
do b
]
ex3: function [[catch]] [r] [
set/any 'r default a [throw error]
do b
]
a: [return "OK"]
b: ["KO"]
ex1 ; == "KO"
ex2 ; == "OK"
ex3 ; == "KO"
a: [()]
b: [if not value? 'r ["OK"]]
ex1; ** Script Error: blk needs a value
; ** Where: throw-on-error
ex2 ; == "OK"
ex3 ; == "OK"
The above tests show that both THROW-ON-ERROR as well as ATTEMPT should be
corrected. I suggest to write them as follows:
throw-on-error: func [
{Evaluates a block, which if it results in an error, throws that
error.}
[throw]
blk [block!]
][
if error? set/any 'blk try blk [throw blk]
get/any 'blk
]
attempt: func [
{Tries to evaluate and returns result or NONE on error.}
[throw]
value
][
if not error? set/any 'value try :value [get/any 'value]
]
Sometimes we need to distinguish between a pass value and a default value.
See the following example:
attempt [none] ; == none
attempt [1 / 0] ; == none
In the first case NONE is a pass value, while in the second one, it is a
default value. Compare that to:
default/good [none] ["default"] ["pass"]
default/good [1 / 0] ["default"] ["pass"]
This is an Achilles' heel of the ATTEMPT function and of any function that
uses NONE as a default value in the cases when NONE can be a pass value too.
The same problem exists with errors. An error can be a pass value as in:
a: head insert [] make error! "OK"
first a
; ** User Error: OK
; ** Near: a: head insert [error] make
, if A is a block containing an error value at its first position and it can
be a default value like in
1 / 0
; ** Math Error: Attempt to divide by zero
; ** Near: 1 / 0
We can distinguish these cases as follows:
error? first a ; == true
error? 1 / 0
; ** Math Error: Attempt to divide by zero
; ** Near: error? 1 / 0
The TRY function cannot discern these cases and therefore we do not have any
general function able to do that. The DEFAULT function isn't working as I
would like it to work, because it uses the TRY function.
A few questions:
- Should Rebol have a native version of the DEFAULT function able to discern
the default and pass cases?
- Should the behaviour of error values change a bit to enable the
interpreter to yield errors as pass values?
- Should we have versions of PICK, SELECT, etc. functions discerning the
pass and default cases?
An unrelated note: The usage of NONE in place of an empty block is not a
Rebol rule. There are cases, when Rebol returns an empty block:
exclude [a] [a b] ; == []
-L