In 2013 there was a bug about a narrow question about FOR semantics in CureCode. Today I want to fix the bug, and cite the issue. But the discussion got really complicated and intertwined with alternate proposals, so it’s unusually hard to read.
The biggest offender who made that thread hard to read is me … where I dodged the subject of the post entirely and just said (basically) “who cares, FOR sucks, let’s talk about something else”. Ladislav more concretely engaged the topic of the issue with a specific proposal and its ramifications. By comparison, it’s much more on topic.
What I said prompted Gregg to ask:
Author: Gregg Irwin
Brian(H), just asking, is there a doc somewhere about what we are and aren’t willing to break in R3? I know FOR is probably used by a lot of people, no matter how much we’ve discouraged it, but it may be worth breaking if we can make things much better going forward.
To which BrianH wrote this reply, that Gregg suggested be put in a wiki. I’m deleting it there and moving it here, so there is some chance at actually reading what the issue is about!!! The original thread is preserved on the Internet Archive
Author: Brian Hawley
The policy is mostly documented in the many places where I’ve mentioned the policy in CC, so many I don’t even have a count. It was set by Carl back at the beginning, so he might have written a blog about it, I don’t remember.
The basic themes are in #666 and #667. Overall, it’s a matter of having to justify breaking things, and in many cases we can, especially when the old function was so broken that noone really uses it (#1973 for instance, #1972 maybe). In other cases, we change the name of the function when it was the name itself that was seriously broken (#1971). In most cases though, we just fix the internal semantics so that the external API will pretty much work the same, but better (like this one). In just a few cases, the API of the old function was so broken or so tied into the old R2 system model that we just had to revamp it even knowing that things would break (LOAD is the only one that comes to mind, with too many tickets to even list in #666). We even try to keep the order of function options the same when we can so it doesn’t break APPLY calls, though if we need to remove an option that becomes less important.
Internal semantic changes are easier to justify than API changes, but only if they’re improvements or fixes. Sometimes we have made some arguments take more or less types, to clean up their models - the rebalancing of none propagation, and the systemic binary conversions revamp, are good examples of those. In one case (issue!) we even changed the semantic model of a datatype from one type class to another, but it’s possible to gloss over that quite a bit since those classes have more in common than they don’t (it’s on my todo list). In one notable case and likely a few more, we might want to revert a few changes or change them again better (at least before 3.0 when core semantics and naming of existing stuff gets standardized again).
If you are replacing an old R2 function with a new one, you need to both justify adding the new function, and separately justify dropping the old. The only reason I’ve seen so far for replacing a function with another function of the same name is if the old name was so bad that it was actively misleading, and we want to make sure the renaming sticks just for our developers’ benefit (the only instance I can recall is #1972, and even that might just result in removing those function names altogether). More often we just add a new function with a new name. And for the most part we only remove an old function altogether if it no longer makes sense in the new system model (AS-STRING and AS-BINARY), or is basically hostile in the new model (ALIAS).
More often, legacy functions might become optional, exported from a module that you don’t need to import if you don’t want to. Modules are intended as the general answer to the question of “Do you need it?”, the answer generally being “Yes, someone needs it, but maybe not by default, and maybe not built in.”; modules are intended as the balance between /Command/View maximalists and /Base minimalists. All current functions are considered to be subject to that culling, especially if they’re mezzanine, as long as they’re not used to implement R3 itself.
One thing that you need to consider though is that natives don’t add any overhead to Rebol when they’re not used. Mezzanines have to be loaded and initialized at startup time, but natives just sit there. You can even reassign their words if some native that you don’t like gets exported, though you might want to check before changing them in lib if they’re being used internally. DO operations aren’t keywords (of DO at least), they’re all pretty much optional. This isn’t PARSE, remember. So it’s hard to justify removing a native from Rebol altogether when people are already using it, since you really don’t need to, and R3 being open source means that you can do whatever you like with your own builds.
In the case of FOR, people actually use it when it makes sense to do so (like me, for instance). They didn’t use it much in R2, but that wasn’t because it was badly designed for its task, it was because it was badly implemented (slow and buggy). But even given that they still used it, which showed that there was some merit to its design. That suggests that we keep the API for the most part, fix the ugly parts of the semantics, and optimize the heck out of it so people will actually want to use it.
And if someone wants to make an awesome dialected general loop, they’ll have to give it a new name like EVERY, because FOR is taken by something that works. Or they can just make their own add-on module and name it whatever they like, their choice.