DEFAULT with TUPLE!...What Does It *Mean*?

The ability to have random side-effects in tuples creates all kinds of nasty potentials.

That is why I made a way to say you only allow tuples to contain evaluations if you also ask for an "invariant" representation of the path to be returned...that you could use in place of the tuple for further assignments.

>> var: 'x

>> tuple: 'obj.(print "side effect" var)

>> set tuple 30
*** Script Error: GROUP!s require /GROUPS in GET or SET:
    obj.(print "side effect" var)

>> [value steps]: set tuple 30
side effect
== 30

>> steps
== @[obj x]

You can then use STEPS instead of the tuple. e.g. set @[obj x] will act like set 'obj.x. (The reason a BLOCK! is used is because not all datatypes would be legal in tuples...a GROUP! could evaluate to anything, like an OBJECT!...which is a perfectly valid thing to set in a MAP! for instance.)

This "Invariant" Form Was Intended For Things Like DEFAULT

The concept was that even if you have evaluations on the left hand side, you will be able to capture where it got the value or absence-of-value from...and then in the event you need to write it back because a default is necessary, you don't have to run the evaluation again:

>> n: 5

>> data.(n: n + 1): default [10]

Internally, DEFAULT can make a GET request of data.(n: n + 1) and get back STEPS with a fixed value for N, like @[data 6]. If it discovers that the value is set to null and needs defaulting, it can then use the steps instead of the tuple as the basis for the SET...so N is not changed twice.

But Other Things Can Change...

All kinds of changes can happen between the GET and the SET in DEFAULT. What about this?

>> data.(n: n + 1): default [data: ~gone~, 10]

We've prevented running imperative code and getting side effects from the tuple...but the side effects can come from other places. Anything you do between the GET and the SET can make the "invariant" steps not so invariant.

Let's say DATA was a BLOCK! at the outset. Should the @[...] steps have actually stored a reference to the block instead of the WORD! data, so that changes to the variable couldn't redirect the invariant reference?

That doesn't work for immediate types. Let's say you have:

>> time: 4:00

>> tuple: 'time.(if true ['hour])

>> [# steps]: set tuple 12
== 12

>> steps
== @[4:00 hour]

Now you can't use that steps to go back and change the original time, because 4:00 is an immediate value. You need to get at the object the time lives in. You'd wind up with a 3-element steps in that case:

>> steps
== @[make object! [...] time hour]

If Nothing Is Going To Be Perfect... Should We Shortcut It?

Let's say you have a tuple like:

obj1.obj2.(if true [obj3]).obj4.date.time

We could say that the "steps" are:

@[obj1 obj2 [...this is obj3...] obj4 date time]

But is that empirically more desirable than just noticing when we cross the line from non-immediate values into immediate values, and preserving only as much as we need?

@[(make object! [...this is obj4...]) date time]

That's enough to write back to obj4...but it is fully disconnected from the tuple that was initially being represented. But which is more suitable for the intent of something like DEFAULT?

My Head Hurts :face_with_head_bandage:

...and it's not because of a booster shot. It's because this is crazy, and way past the point of being fun.

What started out as a nice idea for taming GROUP!s in TUPLE!s becomes a real headache.

So I'm going to leave well enough alone, and say that this does only that. If you want to prune a tuple to get better invariants, that's on you:

For example:

(obj1.obj2.obj3.obj4).date.time: default [...]

When you do this, then when DEFAULT asks for steps it will get 3 items in the block:

>> steps
== @[(make object! [...]) date time]

It has latched onto the object, and so you can say inside the body of the defaulting code things like obj1: null or obj1.obj2.obj3: [] and the assignment will apply to what it was looking at, because those names aren't in the steps.

1 Like