Mailing List Archive: 49091 messages
  • Home
  • Script library
  • AltME Archive
  • Mailing list
  • Articles Index
  • Site search
 

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