Closed IngoHohmann closed 5 years ago
though it may make sense to make function source be locked, having series in the console being locked makes the console a lot less useful.
This could be argued either way; the other perspective is that if something works in the console and you paste it into your ordinary code you will have an inconsistent experience.
As with most things that can be argued either way, this could be controlled with an option. I'm trying right now to find a way to bring back the idea that more of the REPL behavior is written in Rebol, and this could be one of the things that is decided by the behavior of that user-mode code.
My opinion: I find this quite frustrating in my daily usage of Ren-C. I really do not need to copy
everything I am working with.
@kealist Do you mean in the context of the REPL, or the language as a whole?
The language as a whole benefits from it, I believe...with the classic examples like get-string-from-x: func [x] [switch x [1 ["foo"] 2 ["bar"]], and then the caller says append get-string-from-x 1 "-stuff" and is changing the contents of the switch statement.
If you mean the REPL only, then I don't really have a problem with mutability getting into the user settings of those who wish to try it, as mentioned above. But I'd prefer it not be the default, just because it would create an inconsistent experience. You can look at this PR for some developments in customization, and configuring the lock status of code can be addressed there:
REPL is biggest issue, yes, but I don't like this change at all. I understand the intention, but I would rather that the learning curve be there than to have series be immutable by default.
Many parts of previously written (beginner) documentation for Rebol no longer apply to Ren-C.
If it is going this far, I would rather have copying be the default. ie b: []
be syntactic sugar for b: copy []
@kealist Well, in Rebol's own codebase, most of what showed up with were bugs, or bugs waiting to happen. So I'd be interested to see good cases that it's impeding. Basically--cite your examples of why you feel good code is being "broken".
I myself don't feel that particularly put out by the fact that people who used to learn trivial cases like x: [a] | append x [b c] are now getting hit sooner with the realization that this will not serve them in a general way. These simple examples were poor skill-building for use of the language. Those Rebol long-timers who survived the hazing might not completely absorb the potential for greater adoption when the truly infuriating bugs are removed.
Andreas and I in the past considered (and experimented) with the question of whether x: [a [b [c] d] e] could be syntactic sugar for x: copy-on-write/deep [a [b [c] d] e]. It's a little weird if you get different behavior from a literal than you would from x: quote [a [b [c] d] e] but it would be possible to do that.
In any case, I do not think a shallow copy would be very coherent, and I think if the copies were always made--even on data you never planned to modify, it would wind up being wasteful. It would take something you expect to not cost very much in a loop and suddenly make it cost more. Though if you write loop 100000 [foo: func [x] [print x] | foo 10] you are paying for the construction of a function 100000 times. So one could argue that people should be aware that if they don't pull that definition out of the loop they will pay...though you can also address that with loop 100000 [foo: default func [x] [print x] | foo 10] in cases where you know it wasn't set.
In any case, the copy-on-write idea at one time seemed promising, with the main problem being that copy-on-write of a deep block had difficulty really being cheaper than copying the block fully. I felt at the time it was pretty much a fundamental problem which couldn't be fixed efficiently, but we could look at it again.
But... really, I do want to see the good examples of working with mutable series, that came from a literal, that were not copied.
tl;dr immutability may not be so bad after all. What we really need is more tools to manage between mutability and immutability.
Note: Somewhat random thoughts follow, so take with a grain of salt. After writing all this, I remembered this had been discussed a lot e.g. http://www.rebol.net/cgi-bin/r3blog.r?view=0006, and trello mentions persistent vector which is interesting. All we really need now is probably an IDE which supports automatic translation and error checking.
I was really frustrated with this behavior too at first, still am when I'm switching from Red.
Immutability is one of those major backwards incompatible changes from Rebol2 that requires a rewiring of your brain (the other being help
and dump
instead of ?
and ??
.
In practice tho, I can replace most cases of append
and insert
with append-of
and insert-of
, but I need to understand the big picture of how to work with other cases of mutation now. Also a part of me secretly wants immutability (the part that likes Haskell)
The most who will benefit right now is newcomers. "Guru" rebol users sometimes use and even rely on this behavior, it is one of those fundamental "gotcha"s . And @kealist point about documentation also applies to newcomers.
That aside, you can see in that reddit thread Doc argue that it all boils down to there is no code, only data. It's a good point, Redbol can be considered as just data load
ed into memory. However, you could argue his explanation is orthogonal to whether or not it should be immutable.
In that same thread, a comment mentions this is also the same way as is done in smalltalk. I've not had first hand experience with smalltalk, the closest I've come is the factor IDE.. but what I can infer with the help of a comment on hn is it's referring to introspection and so called object oriented (in the original sense!) properties:
Smalltalk gives you amazing tools for monitoring the state of objects in the system. The level of control at every level of execution is unlike any other language. You can open up live inspectors on any object, edit a method in the debugger and restart the stack frame you are looking at, and persist all of it across programming sessions in the image.
This has been my experience with factor (the introspection is built into the debugger, not the language). This enabled by the image like properties common to all 3 languages. Still, not an argument for or against immutability, but for both.
Is there anything immutable structures can't do? Searching didn't turn up anything except a misunderstanding that bidirectional binding is impossible, aka mutually recursive structures
A lot of modern languages like Rust and Julia are immutable by default, (specifically in Julia, there are in-place mutable function!
counterparts to the immutable function
). You could argue this is the case in redbol (and more so in rebol1 !!)
In conclusion, the real advantage of mutability is its initial simplicity (when you don't have lots of concurrent things), and it's performance: potentially less memory and overhead.
tl;dr immutability may not be so bad after all. What we really need is more tools to manage between mutability and immutability.
FEEDBACK AND TESTING IS NEEDED TO PIN THIS ONE DOWN FOR BETA/ONE
In accordance with @geekyi's presicent remark...we now have a new era of potential solutions, due to CONST and MUTABLE values--(as opposed to locking or protecting series):
https://forum.rebol.info/t/source-mutability-const-and-mutable/976
The post shows how it could be applied to Rebol2/Red/R3-Alpha compatibility. And I want mutability for code golf, where you're willing to trade off more potential for bugs for fewer characters of code.
in that reddit thread Doc argue that it all boils down to there is no code, only data.
The only way to tell if it's code or not is when you're executing it. So that's the premise this implicit const is coming from. There's nothing about LOAD-ing something that makes it "source", and I think that's an important difference from some of the direction with the first "source-protection" angle
I think my SWITCH example from the post is extremely compelling to why a stronger solution should be the default. So the question is what is it about it that makes it compelling to people that doesn't also apply to something like x: [a b c]
in the console.
One thought I had was that it could be depth of a wave of execution--that "top level" at a kind of global scope (or at a command prompt) has different priorities, specifically because it isn't iterated. But once you say loop [x: [] ...]
you're no longer at that "topmost level" so the line can be run multiple times. And obviously function bodies have this potential too. Maybe this warrants a different policy in each case, where you would have to say loop [x: mutable [] ...]
but still be able to say x: [a b c]
and append to it.
(So...kind of having a different rule for "global scope"?)
It does concern me that mutability by default--without saying you want it--teaches bad habits that fall down quickly in Rebol. You might argue that in the console, the convenience of not having to type "mutable" is worth it and saving the behavior for modules or larger scripts. But I mentioned to @IngoHohmann that I'm wary of inconsistency in the console, which is where people find their grounding and type things in to test. If it's acting different "in practice" that may not be worth it. And I feel things like the SWITCH example show that "in practice" you really do not want default mutability as much as people seem to think.
Anyway, the first cut at const/mutable is now committed. It's all hacked together...but protection in Rebol always was. I figured it was better to start getting exposure to the method as soon as possible to start trying to make decisions on what the default behavior should be.
The line of thinking I went along with came from this:
One thought I had was that it could be depth of a wave of execution--that "top level" at a kind of global scope (or at a command prompt) has different priorities, specifically because it isn't iterated. But once you say
loop [x: [] ...]
you're no longer at that "topmost level" so the line can be run multiple times. And obviously function bodies have this potential too. Maybe this warrants a different policy in each case, where you would have to sayloop [x: mutable [] ...]
but still be able to sayx: [a b c]
and append to it.
But what I concluded is that it should be nothing to do with "levels of depth". It's specific constructs that decide that <const>
-ing things is important.
Constructs that decide it's worth it are things that are iterative. So loops--but also, though function bodies don't iterate on a single run, they expect the function to be called multiple times. They also <const>
their body argument--such that their view of what you pass in is const.
Included in the design is the ability to override it, and I'm sure more nuances will come out of practical usage. But the foundation looks to be laid. So I consider the overarching issue closed...please open new issues regarding individual behaviors or bugs.
though it may make sense to make function source be locked, having series in the console being locked makes the console a lot less usefull.