Maps: Versatile Interface

In another episode of 'Where should you use maps vs. ...?', was reflecting on this post:

I don't know if maps are the most suitable choice here, but they would certainly be the least awkward expression of such options were they a little more versatile:

load/options %some-resource.reb #(
    tabs: no
    CRLF: yes
)

I'm not a fan of Red's specific literal notation for this, but running with it for the purposes of this thought. If a function were to receive such a map, could you REDUCE or COMPOSE it so as not to have to qualify each word?

reduce options 123
=> #(
    tabs: #[false]
    CRLF: #[true]
)

Would a literal map be bound to its containing context?

reduce use [bar][
    bar: "Bar"

    #(foo: bar)
]
=> #(foo: "Bar")

Red does not support this, but seems a reasonable expectation to me.

Permitting REDUCE which would presumably only reduce values and not keys and most likely creating a copy would have a distinct advantage over the awkward composition of objects in the cases where they have been used in these cases:

do-this-way: func [
    content
    /option1
    /option2
][
    do-this/options content reduce #(
        option1: option1
        option2: option2
    )
]
load/options %some-resource.reb #(
    tabs: no
    cr-lf: yes
)

I'm not a fan of Red's specific literal notation for this

I'm still skeptical on whether investing and spreading the MAP! concept in the core plays to the language strengths at all. It's like we're just going for JSON, but worse.

Besides BLOCK!s looking better, they afford you more options in representation:

[
   numbers: 1 2 3
   thing: (*) specially-marked
   [1]: "maybe SET-BLOCKS are the gateway to having generic keys?"
]

Cracking open the toolset of what it would take to give you "powers" for such blocks would help in dialecting. So focusing here has benefit across the board.

If it's performance people are concerned about, we can make the system speed up lookups when it notices access patterns it thinks it can speed up (e.g. a behind-the-scenes-HASH!)...and you could give it hints. But it would all still be the currency of BLOCK!.

I appreciate the versatility and possibility in your suggestion and trying to gain leverage from the language. However there is the inherent complexity in this even if you bring it to a core level. If all you're looking for is a list of yes/no/this-here/that-there, then maps and the handling thereof seemingly offer an immediate path with some potential perks

Over time I just keep getting more convinced that it is a mistake to try to introduce non-BLOCK! source notations for internally complex data structures.

A guy I know wrote a book called "Thinking Spreadsheet" (that I offered early feedback on), where he tries to motivate techniques of working with spreadsheets that embrace the medium. He talks about how people use spreadsheets as databases:

"On a small scale, you can use spreadsheets as databases. Many people do, and several of our examples will. This is especially convenient when your data fits neatly into a small table and when you intend to interact with your data only through the spreadsheet itself. If your state has issued only 100 license plates, then a spreadsheet might be a good way to store that information."

Rebol notation is like the spreadsheet; it is supposed to be light and easy. Yet the idea of turning such a medium--a textual one--into something that is a generalized store for maps and objects and runtime representations of functions...and a whole menagerie of as-yet-untold user-defined-types...just doesn't hold water.

So the attempt in Rebol to turn all datatypes into renderable forms that might be LOAD-ed back...as part of a serialization...is I think a dead-end. The BLOCK! format makes sense as something that's a little bit better than TEXT! for serializing... but it's only a little bit.

I pointed out how I'd moved away from this with actions, and cited this counter-example carried forward from Red:

red>> append [1 2] :help-string
== [1 2 func [{Returns information about functions, values, objects, and datatypes.} 
    'word [any-type!] 
    /local ref-given? value
][
    clear output-buffer 
    case [
 ...

To me, MAP! is another one of these instances where the strengths of the substrate are being undermined by trying to make it part of the source notation.

I think putting things in a MAP! (or OBJECT! for that matter) should box it up to where you can query it and get BLOCK!s back for serializing them...but you don't expect to mold them and paste them back.

Practically speaking, having observed this particular discussion for as much or as long as there's been any volume of Rebol development, there has been near since the outset a desire for a separate block type that conforms to the key/value pattern in ways that blocks cannot without a significant amount of care, corraling and pretense. There's just no amount of functions, modified path behaviour that can scratch that itch, even if it's wrongheaded. That JSON stepped in and became wildly popular with a tiny subset of types—but crucially this one that Rebol does not, with stringed keys no less—just heightened the sense that this is a glaring omission.

It comes down to representation—yes, you can represent the key/value pattern in blocks, though just because you have values arranged in a key/value order, it can't be assumed. Yes, the key/value pattern can be abused and part of Rebol's strength in types is that you can leverage a certain amount of implied semantics, but that only gets you so far. I believe analyzing the way JSON is deployed—the good and the bad, it's still the broadest demonstration of the Rebol concept—can be illustrative on where maps work and don't with the exchange (data, data-is-code and code-is-data) medium.

Could add ports and other items to that. I'd say MAP stands apart—it doesn't have any more or less context than a block and the implementation is a means to fit the format than some special internal type looking for representation. Actually, I'd say the same about functions to a degree—you could view a function as a block (behaves more group-like) with additional params metadata

Differences lie here, I guess:

>> blk: reduce ['group quote (pi) 'func func [][pi]]      
== [group (pi) func make action! [[return:] [...]]]

>> blk/group
== (pi)

>> blk/func  
== 3.141592653589793