BLOCK! and OBJECT! Parity in Pathing/Picking


#1

Rebol didn't have any great answer for "object literals" and "map literals". Yet literal objects ("dictionaries") are critical to JSON. So it has seemed that Rebol needs an answer here.

Notationally we're at a bit of a loss of how to represent them. But something I began to wonder is if Rebol has been losing some of its advantage of having a SET-WORD! type. If you look at:

array: [foo <baz> #bar]
dictionary: [field1: ... field2: ...]

Couldn't it just be that the presence of SET-WORD!s in the dictionary-like thing is what gives you the tip-off? Why exchange files with literal objects, instead of doing it with blocks? The kinds of data exchanges we see using such forms are not rigorously glued to a schema--by design--the concept is that the sender and recipient are on the same page about things.

There may be little benefit to having another container type saying "this is an object", if objects and blocks act more or less interchangeably when used with keyed access. The user of the data would have the option to make a block into an object if they needed to, or not if they didnt?

I've suggested before that I feel there's something wrong with "pickquality", where it's the idea that picking and pathing consider WORD!, SET-WORD!, ISSUE!, etc. to be equal. I felt that if BLOCK!s are used for keyed access in this way, they should use SET-WORD!s...and then the seeking process in the block can look specifically for them. If we use "scant evaluation", then this could even allow for SET-WORD! values.

Demonstration

block: [
    <meta-tag 1> <meta-tag-2> #meta-issue
    string: "hello"
    x: y: 100
    code: '(1 + 2)
    setword: 'something:
]

>> block/string
== "hello"  ; skips info before set-word!

>> block/x
== 100  ; advances to first non-set-word value

>> code
== (1 + 2)  ; "scant" evaluation, removes quote

 >> setword
 == something:  ; also "scant" evaluation

Imagine being able to manipulate the block in a way that has parity with how it would act if it were an object. So getting the quoting if necessary:

>> block: [thing: 10]

>> block/thing: first [a b c]

>> block
== [thing: 'a]

You don't want path access to trigger an actual evaluation of something that isn't inert or quoted, so that should give an error:

 >> block: [thing: (1 + 2)]
 >> block/thing
 ** Error: keyed access of block only works on inert and quoted types

To me, this really feels like a coherent direction, with something that has been very lacking in coherence historically.

Cowpaths

@rgchris very much likes the freedom in blocks to say [a 1 b 2] and not [a: 1 b: 2]. So his direction of cleanup of the historical behavior would be to treat blocks as collections of 2-element records, which would prevent confusion if you wrote [a b b 2].

I appreciate wanting to have this choice. But for pathing, I like this parity with objects idea. I do not feel like enforcing the SET-WORD!s at odd numbered positions feels like the right choice there...I'd like to be able to be more free-form, as I would be able to do with MAKE OBJECT!. It addresses several dark corners, and I think that it will make blocks stronger for data exchange.

Pathing and selection are already not the same (as block/2 gives you the second item of the block, not an item immediately following an INTEGER! 2 inside the block). If we changed SELECT to a /SKIP default of 2, and went with the SET-WORD!-based behavior for pathing (hence also PICK+POKE), would that make everyone happy?

>> select [a b b 2] 'a
== b

>> select [a b b 2] 'b
== 2

And I'd suggest that SELECT would have you use the data type you were selecting on. So it would not consider SET-WORD! and WORD! the same (nor ISSUE! and WORD!, etc.) With skippable functions to do the equality, you could ask for varying behaviors:

>> select [a 1 10 20] .matches integer!
== 20  ; e.g. a wasn't an INTEGER!, but 10 was (1 skipped going in steps of 2)

And one of those behaviors could ask for a more lax style of comparison which considered SET-WORD! and WORD! to be equal. .synonymizes, perhaps. or .wordequals

>> select [a 1 b: 20] .wordequals first [a:]
== 1

>> select [a 1 b: 20] .synonymizes 'b
== 20

In any case, the idea is that the SELECT command is about treating a block as records of fixed size. I don't know if that means that it should not be used with objects (preferring PICK). It might be clarifying. PICK is a shorter word.

(I also don't know what the applications are to having SELECT default to 2 characters at a time when selecting from strings. But then again, I'm not really sure what sort of scenarios would get people to select out of a string in the first place, if all you get back is a single character. Should it be limited to just ANY-ARRAY!?)


#2

Given your demonstration block, what should block/x: 7 do?


#3

I'd not be against giving this a try—my main objection to the Rebol 2 behaviour was the unexpectedness in the [a b b c] example. It would be good to have a literate counterpart—a refinement of SELECT (and FIND) that echoes the pathing behaviour. Not quite sure what that'd be for the poking equivalent though.


#4

PICK and POKE are the pathing equivalent...I'm not sure what you mean. What aspect would SELECT or FIND need to emulate?

If you mean "word matches set-word" I'm proposing that would be done with a comparison operator modification, that would open the doors to other things (I already dislike how FIND of INTEGER! matches both the datatype integer and instances of integer...it seems to me that this is better managed explicitly as one or the other, so find data .matches integer! would find an integer value while find data integer! would be the same as find data .is integer! (assuming lax equality defaults continue)

(And note find data '.matches or find data lit .matches would explicitly look for .matches in the data)


#5

Good question, also what would block/y: 30 do. Presumably they'd either have to error -or- detach the set from the related instances, copying values if necessary. Also to consider: block: [final:] block/final: 10

Red recently revived old debates over whether SET-PATH! should be able to add to blocks, or if you should have to use append. OBJECT!s don't let you set or get keys that aren't there (which protects against typos), and don't let you delete keys...because integer-based existing bindings would be invalidated if those slots wound up being reused.

But I guess the thing to think about here is common use cases. Perhaps the real parallel is to MAP!, and arbitrary additions and removals should be allowed. I don't know, but either way there are more tools available to think about this.


#6

As another point on BLOCK! and OBJECT! parity, people seemed to think you could SET an OBJECT! to a block.

rebol2>> o: make object! [x: 10 y: 20]

rebol2>> mold o
== {make object! [
    x: 10
    y: 20
]}

rebol2>> set o [30 40]
== [30 40]

rebol2>> mold o
== {make object! [
    x: 30
    y: 40
]}

Here we see a hardened assumption that objects have fields and they are ordered. Red has carried this assumption forward.

It complicates features that try to be "more than blocks". e.g. when R3-Alpha added "hidden" fields there was no definition of what SET via BLOCK! would do to it, so it just said "not allowed: would expose or modify hidden values."

But it suggests the kinship to blocks being far closer than one would think. This raises deep questions for the system. And I feel like there's no way to answer those questions without putting stakes in the ground and listing the things that must work.

(Clearly hiding fields doesn't have to work with SET. What's the justification that all bets are off then?)