{ Rethinking Braces }... as an array type?

I've historically been pretty attached to braces for strings. They sure can be nice.

But I am increasingly thinking braces might be better applied as a new array type:

>> bracey: first [{This [would] be @legal}]
== {This [would] be @legal}

>> length of bracey
== 4

>> second bracey
== [would]

(UPDATE: I think this idea received its due consideration over the span of a couple of weeks...but doesn't weigh in as worth it to change what we use for strings. Explanations laid out in the thread below. But keeping the proposal intact for historical reference.)

So it would act like a BLOCK! or a GROUP! when it was inert. But the real benefit would be the idea that if this braced form got evaluated, it would effectively do a MAKE MAP! or MAKE OBJECT! (or "something along those lines")

>> obj: {x: 10 y: 20, z: 30}
== make object! [  ; whatever this representation is, it's not {x: 10...}
    x: 10
    y: 20
    z: 30
]

This kills two birds with one stone: A neat new dialecting part that would also give a better source notation for objects!

Having its evaluator behavior be "make an object/map" pushes this from "frivolous third form of block" to being clearly useful on day 1. But I think the block form would soon turn out to not be frivolous.

Carl Himself Wants To Move Away From Braced Strings

In Carl's "ASON" pitch, he moves away from Rebol's choice to make braces an asymmetric string delimiter:

  • "Braces {} are used to denote objects. They are lexical and may be used directly without evaluation (the make constructor is not necessary)."

  • "Braces {} are not used for multi-line strings. A single+double quote format is used for multi-line strings."

I must admit braced strings can make a lot of situations in the text programming world look better than they typically would.

But it comes at a cost for taking the asymmetric delimiter, and is a real weakness against JavaScript and JSON. When rethought as this fun new dialecting part, it actually offers a new edge and plays to Rebol's strengths.

What might the new {...} type do in PARSE? As a branch type? In your own dialects?

My {...} Proposal Is Arrays, Not Object Literals

It might seem like having a source representation of objects that maps directly to the loaded/ in-memory representation would be better. But in practice, you can't really get the loaded form to ever look completely like the source...there's so many issues with nested cyclical structures or things that just don't mold out.

It doesn't work in JavaScript either. Note that you're not supposed to be loading JSON directly in any case into JavaScript...you're always supposed to go through parsers and serializers. So that should be weighed here when looking at the suggestion of a structural type that happens to evaluate to give you an in-memory representation.

Map Representation Via : ?

There was another remark in the Altscript on the role of colon:

For JSON compatiblity:

  • Keys (word definitions) can be written with quotes ("field":)
  • A lone colon (:) will automatically associate to the word/string immediately before it.
  • Commas as element separators are allowed as long as they are not directly followed by a non-digit character (to avoid confusion with comma-based decimal values.)

The note about the colon seems like it might be good for maps.

mapping: {
    1 : "One"
    "Two" : 2
}

This could help avoid the need for SET-INTEGER! or similar.

3 Likes

Yes, "make object!" must die. I have wasted much time in the past trying to work out how to use blocks instead of objects just to avoid the mess that objects make on mold of a structure.

Yes please!

Damn, that's true, but surely there's a solution somewhere. Nesting should be straight forward, but cycling I guess needs to represent a context reference and we don't have identifiers for contexts.

123 : {bizarre-thought : "is something like this the new type of" "context" cycling: @123}

But if that was feasible and allowed, would there be any significant difference between { and [ ?

2 Likes

Well my point is about ambiguity:

 >> first [{a: 10 b: 10 + 20}]
 == {a: 10 b: 10 + 20}  ; a BRACED! of length 6 (or whatever)

>> {a: 10 b: 10 + 20}
== ???a 10 b 20???  ; an object or map or dictionary or something.

There would be some kind of semi-serialization operator:

>> obj: {a: 10 b: 10 + 10}
== ???a 10 b 20???

>> bracify obj
== {
    a: 10
    b: 20
}

Which could navigate you from the object from "what would create me". But this has limits.

and maybe that's what MOLD is. (Not that I ever liked the name "mold"... SERIALIZE ?)

I'm just saying that the console needs to keep you grounded when you're talking about something that's in memory and structured vs. the source array notation. They're different and it's a losing battle to not keep people aware of that difference.

2 Likes

17 posts were merged into an existing topic: Alternate String Forms if {...} Becomes An Array Type

In Carl's "ASON" pitch, he proposes lexical braces to mean objects:

I don't think this would satisfy JavaScript programmers...nor Rebol programmers for that matter...as a replacement for MAKE OBJECT!. (Note there is no source code for ASON, so we don't know how many scenarios he has looked at or hasn't.)

The problem is that if you take braces to mean objects literally, it raises the question of when expressions would be evaluated... or if expressions are allowed at all.

You can't evaluate expressions at LOAD time because the variables and contexts are not set up. So what would you get from this?

center: {x: 10, y: 20}

points: [
     {x: center.x - 10, y: center.y - 10}
     {x: center.x + 10, y: center.y + 10}
]

print ["Point 1's X is" points.1.x]

See the problem? When could these expressions be evaluated? They couldn't be evaluated at load time (it doesn't know what CENTER is yet, the line assigning it hasn't happened). And it can't be when POINTS is assigned, because the BLOCK! is inert.

It seems you simply can't use expressions when braces are used to lexically represent an object instance.

How Does Red's "Lexical MAP!" #(...) Deal With This?

It doesn't.

red>> points: [#(x: 1 + 1 y: 2 + 2)]
== [#(
    x: 1
    +: 2
    y: 2
)]

Since it couldn't evaluate the expressions it just did something weird, as if you'd written:

 points: reduce [make map! [
     x: 1
     +: 1
     y: 2
     +: 2  ; overwrites earlier mapping of + -> 1
]]

:roll_eyes:

So if you want expressions, you need to go through the normal process of MAKE MAP!.

How Does JSON Deal With This

It doesn't.

JSON is narrowly defined to not allow expressions...because it is for exchanging data.

You are discouraged from actually using a plain JavaScript eval() to process a JSON message precisely because it can evaluate...which could lead to bugs or vulnerabilities.

That doesn't change the fact that {...} within the JavaScript language evaluate when the line containing them is run. In this usage they are called object initializers.

How Would The FENCE! Proposal Handle This?

The proposal differs from "{} as lexical object" because it says that FENCE! is just another category of array...which when evaluated gives you an OBJECT!.

Arrays would be a case where this would surprise a JavaScript programmer, because they expect an array initialization to run evaluations of the object initializers inside that array. That's because arrays get evaluated without a REDUCE step.

But this applies for any expression, not just objects:

js>> var test = [1 + 2, 30 + 40]
js>> test
<- Array [ 3, 70 ]

js>> var test = [{x: 1 + 2, y: 30 + 40}]
js>> test
<- Array [ {...} ]
   > 0: Object {x: 3, y: 70}
     length: 1

We understand that the first case in Rebol would need some sort of evaluation request like REDUCE to run the expressions. With FENCE! the way I propose it, there would have to be a similar request for evaluation.

This calls for operators that parallel JSON.deserialize(), that would enforce that there weren't expressions. But also operators that would be more COMPOSE-like...looking for all the OBJECT!s in nested arrays and generating new BLOCK!s of OBJECT!s from them.

What About Accessing FENCE! In Object-Like Ways?

There's historical precedent of being able to pick from blocks like they were objects. Fences could try that as well.

>> points: [{x: 10, y: 20}]
== [{x: 10, y: 20}]

>> type of points.1
== #[datatype! fence!]

>> points.1.y
== 20

But this leads to complex questions. What if they are expressions? (The accessor could error if it doesn't see a comma?) How would NULL be represented? (Going directly to the next SET-WORD?) FENCE! is a generic array so you can APPEND arbitrary material to it...what happens if you add another X?

I don't know how beneficial it is to go along with this illusion, but the subject matter already has a post for discussing it:

BLOCK! and OBJECT! Parity in Pathing/Picking

One nuance here is that if braces are used principally for objects, then maybe @rgchris's wish for BLOCK!s to think in terms of /SKIP counts (alternating keys and values?) could coexist with a SET-WORD!-seeking rule for FENCE!.

Summary...?

The lexical brace concept (which I have never lent my support to) would not replace MAKE OBJECT! because it cannot be used for expression evaluation. It would be creating the objects it represents at LOAD-time, not when expression contexts were known. It would thus serve a very niche purpose in making data exchange somewhat-more JavaScript compatible.

The FENCE! proposal would be able to replace MAKE OBJECT!, but would be essentially a synonym for that functionality today. You would still need to be in an evaluative context to get the OBJECT!...and until then it would remain as an unevaluated FENCE!. But at least you can use it for more than just data exchange. And on the plus side, this opens up fences for interesting dialecting purposes.

2 Likes

I feel like I've "disproved" the value of braces for lexical objects. And so the somewhat glum finding is that you aren't really getting all that much bang for your buck out of a new array brace type that you couldn't do before with MAKE OBJECT!.

In fact, the new brace type couldn't be used a lot of places. You wouldn't want to derive objects using it, because it would greedily create objects:

base-object: {a: 10, b: 20}
derived-object: extend base-object {c: 30}

This would wind up creating a useless intermediate OBJECT! with a key of C and a value of 30, because the parameter would be evaluated before EXTEND would receive it. You'd be better off passing [c: 30] so the structure could be analyzed and integrated into a single new created object.

These sorts of finding rains on the parade of the idea of braces for objects. Which led me to wonder...

What If We Used GROUP!s in Serialization?

>> stuff: reduce [make object! [a: 10 b: 10 + 10]]
== [object!#[a: 10 b: 20]]  ; or whatever internal representation

>> serialize stuff 
== [(a: 10 b: 20)]

>> deserialize stuff
== [object![a: 10 b: 20]]

JSON doesn't use parentheses in this way because it can't. Parentheses are not reified.

But if a Rebol system wants to exchange information with another Rebol system "in the style of JSON", GROUP! could be used to represent the key/value objects and BLOCK! could represent plain array/lists...with braces being a handy mode of string representation that can get away with less escaping.

And with modern COMPOSE you could deserialize and compose, by labeling the compose sites:

>> deserialize compose <*> [
    (a: 10 b: 20)  ; deserialize treats as object
    (<*> reduce [1 + 2 3 + 4])  ; array before deserialize
 ]

Whatever you call it, having something like COMPOSE which treats nested levels of GROUP!s as object creation requests isn't that crazy an idea. (Perhaps OBJECTIFY ?)

I think it has been good to do a deep dive into the "brace-envy" of JSON, and to think about greater compatibility there. But I sense pursuing that may be a bit of a mirage. I'm still thinking about it, as well as what a realistic role is for a Rebol-type system in the software world...but I'm getting more skeptical.

4 Likes

If anyone could make this work in Rebol, it would be Carl. But it just might not feel like Rebol to most.

I believe that's the why, in his response to ASON. He really believes this could be a better way to do a multifaceted approach with exchanged data.

I think Altscript would set the rules for evaluation. And you would through rules set Altscript.

I know someone else crazy enough to make this work.

Hostilefork...

Yeah im just a little crazed that Rebol didn't do this out of the block.. em, box from the beginning to help with code compatibility from the get go.

So maybe let Rebol-Dom inspire you. Not the code, Lordy its all just prototype, but the idea of how the "{}" are being used.

How the DOM or node-element is the arrayed pack_age and the array-obj! var is the set builder notation.

It works very well with "{}" as an object, array, a replacement for Rebol blocks, and the brace is still used for interpolated printing.

It for me makes dialecting very easy. I can set the data to look like almost anything and still process it. Thank you "{}"s.

Once the protos is done i hope to get this to run in Ren-C and Redbol. Who knows, in 5 to 10 years from now maybe even ASON.

Well, actually it's json who got it wrong.

1 Like

Starts at 20:33 into the video, Rebol reference about a minute later.

Going further into this "the way out is through" idea, let's re-envision an example from json.org:

{"glossary": {
    "title": "example glossary",
    "GlossDiv": {
        "title": "S",
        "GlossList": {
            "GlossEntry": {
                "ID": "SGML",
                "SortAs": "SGML",
                "GlossTerm": "Standard Generalized Markup Language",
                "Acronym": "SGML",
                "Abbrev": "ISO 8879:1986",
                "GlossDef": {
                    "para": "A meta-markup language...",
                    "GlossSeeAlso": ["GML", "XML"]
                },
               "GlossSee": "markup"
            }
        }
    }
}}

I think allowing spaces in the keys is a weakness and not a strength. And Rebol doesn't have the historical problem of disallowing language "keywords" in the keys, so the quotes wouldn't be necessary (they're not in modern JavaScript either for that reason, but they are if your word has dashes or spaces in it).

Let's drop the quotes and turn all the braces into brackets. Commas can be optional now, but let's say we don't care to use them when things are on different lines.

[glossary: [
    title: "example glossary"
    GlossDiv: [
        title: "S"
        GlossList: [
            GlossEntry: [
                ID: "SGML"
                SortAs: "SGML"
                GlossTerm: "Standard Generalized Markup Language",
                Acronym: "SGML"
                Abbrev: "ISO 8879:1986"
                GlossDef: [
                    para: "A meta-markup language..."
                    GlossSeeAlso: ["GML", "XML"]  ; can use commas if we want?
                ]
                GlossSee: "markup"
            ]
        ]
    ]
]]

In practice, the serializer/deserializer could say that any block starting with a SET-WORD! is presumed to be an object...and if you have an array that you don't want to get this treatment you use '[...]

>> deserialize "[[a: 10 b: 20] '[c: 10 <random stuff> d: 20]]"
== [
    #object![a: 10 b: 20]  ; whatever notation
    [c: 10 <random stuff> d: 20]
]

A simple rule about SET-WORD!s could give us the same object vs. array distinction in what's being transferred. It would keep us centered on one nice bracket form to be hitting... allow an escape route for arbitrary BLOCK!s that want SET-WORD!s via quote...and we keep our nice braced strings without the need for nasty escapes.

Just trying to reframe the JSON envy, which I'm feeling like we have explored and I'm pretty much deciding against.

Could This Deserialization Be Done By A New Type?

If we were going to make the loading behavior come from a datatype, it might do heavier lifting that the braces did with its /DEEP semantics...interpreting more than one object at a time.

>> obj: &[[a: 10 b: 20] [a: 30 b: 40]]
== [#objectmap![...] #objectmap![...]]

>> data: [[a: 10 b: 20] [a: 30 b: 40]]
>> obj: &data
== [#objectmap![...] #objectmap![...]]

>> data: [[a: 10 b: 20] [a: 30 b: 40]]
>> obj: &(reverse data)
== [#objectmap![...] #objectmap![...]]

>> data: [[a: 10 b: 20] [a: 30 b: 40]]
>> obj: & reverse data 
== [#objectmap![...] #objectmap![...]]

It might matter less that it is ugly, because it's only at the top level. And it could have a name if people didn't like the symbol (I've proposed calling it MAKE-XXX!)

($ is prettier but I've been kind of wanting to reserve $ for shell and environment variable things.)

What About Evaluative Things In Lists?

The lightness of unadorned WORD! has a big draw in Rebol, and has caused a lot of headaches...for instance in deciding if we should say Type: 'module or Type: module.

While there's no rule that says module headers have to obey the same rules as whatever this operation is, it feels unwise to have them deviate. So this implies Type: 'module or moving to something inert like Type: #module or Type: "module"

The problem expands to lists, where you have category: [fun script] needing to mitigate or avoid evaluation one way or another:

category: '[fun script]

category: ['fun 'script]

category: ["fun" "script"]

category: [#fun #script]

While the first option of putting a quote on the list seems like the cleanest, things trying to generate serializations wouldn't have that choice if it was mixing words and objects:

mix: ['word-one [a: 10 b: 20] 'word-two]

If the outer block were quoted, then it wouldn't dig in to make the inner block into an object.

These kinds of odd mixtures of evaluation with objects points out a not-uncommon Rebol problem... if you're doing a deserialization and descending into a block that isn't quoted, you might be seeing BLOCK!s that are arguments to functions along with blocks that are meant to act as objects. Which wins?

Code like PRINT goes step by step, as opposed to gathering all the strings ahead of time and assuming it is for itself:

>> print ["hello" reverse "dlrow"]
hello world

The deserialization operator could work the same way, though it could effectively COMPOSE the BLOCK!s representing objects in so that any functions would be passed the object. :-/

Certainly raises some questions, but, they are fairly common Rebol questions.

...or...REDUCE requests could be explicit?

We don't have to make "recurses to look for blocks to instantiate objects" also imply that the arrays themselves are evaluative, but I think it would be confusing. JavaScript programmers expect the arrays inside objects to be evaluated:

 let x = {label: "object", data: {label: "array", data: [1 + 2, 10 + 20]}}

This gives you a nested structure with data: [3, 30]. So I feel like this operation should follow suit, reducing blocks unless you suppress that.

So if your input is:

[label: "object", data: [label: "array", data: [1 + 2, 10 + 20]]]

I think data should be [3 30], and if your input is:

[label: "object", data: [label: "array", data: '[1 + 2, 10 + 20]]]

Then data should be [1 + 2, 10 + 20]. The mixing and matching really is where you run into trouble, of an array that contains some blocks and some objects...though everything can be represented thanks to generic quoting, it could get messy.

1 Like