Search code examples
dictionarykeyrebolrebol3

Common way of testing for membership in a MAP! working in both Rebol 2 and 3?


In Rebol 2 there was a HASH! type as well as a MAP! type. Both of which were supported by the FIND and SELECT functions, as well as by path-based selection:

>> m: to map! [someKey someValue]
== make hash! [someKey someValue]

>> m/someKey
== someValue

>> select m 'someKey
== someValue

To detect that a key wasn't in the map, you could use FIND and test against NONE

>> find m 'someOtherKey
== none

But path-based selection would get an error in this case:

>> m/someOtherKey
** Script Error: Invalid path value: someOtherKey
** Near: m/someOtherKey

On the other hand, Rebol 3 only has MAP!. But FIND and SELECT only support series types, and MAP! is not considered to be a series any longer. The only way I see (?) to interact with maps is through path selection, which does not throw an error in the non-membership case:

>> m/someOtherKey
== none

...and if your key is in a variable (or a string) you have to use PAREN!

>> var: 'someKey
== someKey

>> m/(var)
== someValue

That works in Rebol 2 also, but with the same caveat of throwing an error instead of returning NONE when you ask for something that doesn't exist.

So if I'm getting this right, path selection is the "common" way of retrieving values from keys in Rebol 2 and 3. Despite that, I'm not seeing a common way of testing for lack of membership. How does one handle this?


Solution

  • Three things to note:

    1. Most immediately pertaining to your question: select works fine in Rebol 3 as well, as the following interaction shows:

      >> system/version
      == 2.100.111.4.4  ; That's Rebol 3 A111.
      
      >> m: map [a 1 b 2 c 3]
      == make map! [
          a 1
          b 2
          c 3
      ]
      
      >> select m 'b
      == 2
      
      >> select m 'd
      == none
      

      By looking at the spec of select in R3 (using e.g. ? series), we also quickly find out that it accepts values of the following types as first argument: series!, port!, map! (!), object!, none!.

    2. R2 does not really have a map! type. It is faked using hash! as part of the "R2/Forward" forward-compatibility layer for R2. (Look at what result you get from to map! [a 1 a 2] in R2 and R3 to see how this may hit you.)

    3. Note that with select you can not really distinguish between keys without any associated value or keys associated with #[none] as value. If you really need that distinction, it's easy to do in R3, where you can use words-of to retrieve the keys of a map!:

      >> words-of map [a 1 b 2]
      == [a b]
      

      However, that was not yet back-ported to R2.

      One more portable way which, however, may be considered relying on implementation internals, is to convert map! (R3) or hash! (R2) back to a block!, and using extract to obtain only the keys:

       >> m: to map! [a 1 b 2]
       >> extract to block! m 2
       == [a b]