The Beta/One Mutability Manifesto


#1

We know that having it be too easy to create self-modifying code in Rebol is a bug-riddled trap. Locking source has shown a hint at what the system can do to help.

Yet we also know it’s very inconvenient to not be able to modify series in the console. Beyond convenience, certainly a lot of historical code hinges on it.

Without further ado, the solution…

Best of Both Worlds: Mutable Default, Case-by-Case Constness

You can now mark the arguments to functions as <const> (as well as apply it manually using CONST). Then if you DO something that’s const, it will propagate that wave of constness down through any literal values it sees when it executes them.

>> do [b: [] append b 10]  ; DO doesn't have its parameter marked <const>
>> b
== [10]

>> do const [b: [] append b 20]  ; but you can add CONST explicitly
** Access Error: CONST or iterative value (see MUTABLE): []

>> loop 2 [b: [] append b 30]  ; LOOP has its `body` parameter marked <const>
** Access Error: CONST or iterative value (see MUTABLE): []

>> loop 2 [do [b: [] append b 40]]  ; LOOP's const view propagates into the DO
** Access Error: CONST or iterative value (see MUTABLE): []

>> block: [b: [] append b 50]
>> loop 2 [do block]  ; LOOP's "wave" of constness only affects inline literals
== [50 50]

>> loop 2 [b: mutable [] append b 60]
>> b
== [60 60]  ; MUTABLE can take in const "literals" in and un-consts them

What are the implications for the console? Well, the console just runs the code you give it with DO, so…

>> b: [] 
>> append b 70
>> b
== [70]

In addition to looping constructs, FUNC and FUNCTION have their body argument marked <const>.

It cures what ails ya

Remember that super-nasty problem I talked about, where you could accidentally return literals from a function body and callers could mutate them?

rebol2>> symbol-to-string: function [s] [
       switch s [
           + ["plus"]
           - ["minus"]
       ]
   ]

rebol2>> p: symbol-to-string '+
== "plus"

rebol2>> insert p "double-" append p "-ungood"
== "double-plus-ungood"

rebol2>> symbol-to-string '+
== "double-plus-ungood"  ; Oh noes

That’s not a theoretical thing. A situation of that nature bit me once and took hours to find. I’m not the only one–and of course newbies (and experts alike) are bit by simpler variations of this all the time.

But now…

>> symbol-to-string: function [s] [
       switch s [
           '+ ["plus"]
           '- ["minus"]
       ]
   ]

>> p: symbol-to-string '+
== "plus"

>> insert p "double-" append p "-good"
** Access Error: CONST or iterative value (see MUTABLE): "plus"

>> p: symbol-to-string '+
== "plus"

>> p: mutable p

>> insert p "you-" append p "-asked-for-it"
== "you-plus-asked-for-it"

>> symbol-to-string '+
== "you-plus-asked-for-it"

That is COOL. Don’t let anyone tell you it isn’t. :slight_smile: There may be hope for mechanisms to implicate where const was added on things–or at least a debug mode where it favors storing that information over remembering the file and line. It’s a balance of cost for such things, though.

Easy to Make Rebol2-Compatible FUNC/WHILE/etc. Variations

We haven’t pinned down the dialect exactly, but it’s possible to make tweaked versions of functions that have differing parameter conventions. Ignore the gnarly syntax for this (it will improve), but:

>> func-r2: reskinned [body [block!]] adapt :func []  ; no <const> on skinned body

>> aggregator: func-r2 [x] [data: [] append data x]

>> aggregator 10
== [10]

>> aggregator 20
== [10 20]

Really, when you consider the possibility for error (again think of the “plus” example above), it seems a lot more prudent to have to say data: mutable [] to get this behavior.

It’s Good…but It’s Still New…so TEST IT!

As I keep saying about these things, I’d love to sit around writing test cases and reasoning about them. But I sort of have to pick at a few tricky cases where I think there may be a fundamental flaw…and if I feel like it’s covered, I have to go on to the next big thing.

There’s little things to wonder about. For instance, it only applies this const rule to inert values. This means you can subvert it with quoting, because the quote causes an evaluation…like how getting a block out of a WORD!-evaluated variable works:

>> loop 2 [append '[] 10]
== [10 10]

Bug, or feature? From the perspective of implementing the API, this is a feature–it means that when you say:

REBVAL *block = rebRun("[1 2 3]");
rebElide("loop 2 [append", block, "10]");

The implicit quote put on the block during the course of the execution is enough to make appending to it legal. It didn’t go through a WORD!-fetch…it went through a C-variable-fetch, so it would look like an inert without that quote.

Anyway… PLEASE revisit your source and any calls to MUTABLE you may have…or superfluous COPYs, and let me know of any problems with this model as soon as possible. Because this is looking good for Beta/One and if we can call it pinned down, that solves one of the largest outstanding questions.


#2

Cool, but it seems to me not so immediate to “see” if something is const vs mutable.


#3

Well, very little in Rebol is obvious to see if you don’t make an effort to make the code clear. You don’t know where function calls begin or end, you don’t know if something is being quoted at the callsite by the function you are calling or not… you don’t know that a SET-WORD! is assigning or just a dialect part (…etc. etc. etc…)

If you want to be explicit you can put CONST or MUTABLE in as an annotation where you think it adds clarity, even if it’s not strictly needed. For instance, when you have:

>> block: [append d: [] <item>]
>> loop 2 [do block]
== [<item> <item>]

>> loop 2 [do [append d: [] <item>]]
** Access Error: CONST or iterative value (see MUTABLE): []

One usage of CONST and MUTABLE would be to change the policy:

>> block: [append d: [] <item>]
>> loop 2 [do const block]
** Access Error: CONST or iterative value (see MUTABLE): []

>> loop 2 [do [append d: mutable [] <item>]]
== [<item> <item>]

Or you can use them to reinforce the policy:

>> block: [append d: [] <item>]
>> loop 2 [do mutable block]
== [<item> <item>]

>> loop 2 [do const [append d: [] <item>]]
** Access Error: CONST or iterative value (see MUTABLE): []

But an underlying great thing about this solution is that it’s not about putting locking into LOAD or somehow building it into the evaluator in a way you can’t turn off. I’ve shown the Rebol2 compatibility, and it can be pushed around as people like. Truly flexible, and the plus example shows how this is desperately needed.