20

I am interested in the idea of C++-like const not that particular execution (like casting away const).

Take for example C# -- it lacks C++-like const, and the reason for it is the the usual -- people and time. Here additionally it seems the C# team looked at the C++ execution of const, marketing CLR and had enough at this point (see why there is no const member method in c# and const parameter ; thank you Svick). Be if we move further than that, is anything else?

Is there something deeper? Take for example multi-inheritance -- it is usually seen as hard to grasp (for the user) and thus not added to the language (like diamond problem).

Is there something in NATURE of const that pose a problem that it is better for language to avoid it? Something like deciding if const should be deep or shallow (having const container does it mean I cannot add new element or alter existing elements as well; what if elements are of reference type)?

UPDATE: while I mention C#, and thus making a historical perspective I am interested in the nature of potential problems of const to the language.

This is not a challenge of the languages. Please ignore such factors as current trends or popularity -- I am interested only in technical issues -- thank you.

greenoldman
  • 1,506
  • 1
  • 14
  • 27
  • 3
    as an aside: multiple inheritance may not be hard to grasp, but method resolution can become quite ugly. Naive depth-first resolution quickly becomes unintuitive (“[diamond problem](https://en.wikipedia.org/wiki/Diamond_problem#The_diamond_problem)”). The [C3 method resolution order](https://en.wikipedia.org/wiki/C3_Algorithm) (published '96) solves this, but is certainly *not* easy to understand. Outlawing multiple inheritance can therefore often seem quite sensible – but the real problem is that Java predates the C3 algorithm, and C# oriented itself after Java. – amon Mar 04 '14 at 21:30
  • 3
    @amon A lot of people don't really think multiple inheritence is a feature you want to have in the first place, e.g. the creators of the D programming languge: *It's a complex feature of debatable value. It's very difficult to implement in an efficient manner, and compilers are prone to many bugs in implementing it. Nearly all the value of MI can be handled with single inheritance coupled with interfaces and aggregation. What's left does not justify the weight of MI implementation.* [(source)](http://dlang.org/overview.html) – jcora Mar 04 '14 at 22:21
  • 5
    Duplicate of http://stackoverflow.com/q/4150478/41071. – svick Mar 04 '14 at 23:31
  • 2
    Even *without* Multiple Inheritance, you can *already* encode any 3-SAT problem as Method Resolution (or more precisely Overload Resolution) in C#, thus making it NP-complete. – Jörg W Mittag Mar 05 '14 at 03:00
  • 1
    @svick, thank you very much. Since I didn't have any answers for this question I took a liberty of heavily editing it, to focus on the nature of the problem, not the historical reasons, because it seems 99% of them was "time+people+anti C++ bias+CLR". – greenoldman Mar 05 '14 at 06:52
  • 1
    I have to disagree that you can "safely ignore such factors as current trends or popularity" in language design. A programming language is a tool for humans. Worse, it is a tool for human collaboration. There would be no point adding `const` to machine code. So the question of what goes into a language is very much driven by the humans you hope will use it. There are tons of esoteric languages that have all kinds of strange mechanisms as part of their nature http://en.wikipedia.org/wiki/Esoteric_language – sea-rob Mar 05 '14 at 09:27
  • 1
    @RobY, with all due respect, I am the person who is asking, so I know what I want to learn. And I am not interested in current trends, but in principles of pure language design (theory). – greenoldman Mar 05 '14 at 10:55
  • 1
    @greenoldman Trends are one of the indicators of what design decisions in a given language are good/successful/bad/unsuccessful. They cannot be _completely_ removed from each other. – Izkata Mar 05 '14 at 19:33
  • 1
    @Izkata, I am not interested in designing successful language. Period. I hope that is enough (I don't know where this trend is coming from to tell me what I think). – greenoldman Mar 05 '14 at 20:42

4 Answers4

12

Note sure if this qualifies for you, but in functional languages like Standard ML everything is immutable by default. Mutation is supported through a generic reference type. So an int variable is immutable, and a ref int variable is a mutable container for ints. Basically, variables are real variables in the mathematical sense (an unknown but fixed value) and refs are "variables" in the imperative programming sense - a memory cell that can be written to and read from. (I like to call them assignables.)

I think the problem with const is two-fold. First, C++ lacks garbage collection, which is necessary to have non-trivial persistent data structures. const must be deep to make any sense, yet having fully immutable values in C++ is impractical.

Second, in C++ you need to opt into const rather than opt out of it. But when you forget to const something and later fix it, you'll end up in the "const poisoning" situation mentioned in @RobY's answer where the const change will cascade throughout the code. If const was the default, you wouldn't find yourself applying const retroactively. Additionally, having to add const everywhere adds a lot of noise to the code.

I suspect the mainstream languages that followed (e.g. Java) were heavily shaped by C and C++'s success and way of thinking. Case in point, even with garbage collection most languages' collection APIs assume mutable data structures. The fact that everything is mutable and immutability is seen as a corner case speaks volumes about the imperative mindset behind popular languages.

EDIT: After reflecting on greenoldman's comment I realized that const isn't directly about the immutability of data; const encodes into the type of the method whether it has side effects on the instance.

It's possible to use mutation to achieve referentially transparent behavior. Suppose you have a function that when called successively returns different values - for example, a function that reads a single character from stdin. We could use cache/memoize the results of this function to produce a referentially transparent stream of values. The stream would be a linked list whose nodes will call the function the first time you try to retrieve their value, but then cache the result. So if stdin constains Hello, world!, the first time you try to retrieve the value of the first node, it'll read one char and return H. Afterwards it'll continue to return H without further calls to read a char. Likewise, the second node would read a char from stdin the first time you try to retrieve its value, this time returning e and caching that result.

The interesting thing here is that you've turned a process that's inherently stateful into an object that's seemingly stateless. However, it was necessary to mutate the object's internal state (by caching the results) to achieve this - the mutation was a benign effect. It's impossible to make our CharStream const even though the stream behaves like an immutable value. Now imagine there's a Stream interface with const methods, and all your functions expect const Streams. Your CharStream can't implement the interface!

(EDIT 2: Apparently there's a C++ keyword called mutable that would allow us to cheat and make CharStream const. However, this loophole destroys const's guarantees - now you really can't be sure something won't mutate through its const methods. I suppose it's not that bad since you must explicitly request the loophole, but you're still completely reliant on the honor system.)

Secondly, suppose you have high-order functions - that is, you can pass functions as arguments to other functions. constness is part of a function's signature, so you wouldn't be able to pass non-const functions as arguments to functions that expect const functions. Blindly enforcing const here would lead to a loss of generality.

Finally, manipulating a const object doesn't guarantee that it's not mutating some external (static or global) state behind your back, so const's guarantees aren't as strong as they initially appear.

It's not clear to me that encoding the presence or absence of side effects into the type system is universally a good thing.

Doval
  • 15,347
  • 3
  • 43
  • 58
  • 1
    +1 thank you. Immutable objects is something else than C++ const (it is more of a view). But say, C++ would have const by default, and `var` on demand would leave only one problem -- the depth? – greenoldman Mar 05 '14 at 13:56
  • 1
    @greenoldman Good point. It's been a while since I used C++ so I forgot you can have a `const` reference to a non-`const` object. Even so, I don't believe it makes any sense to have a shallow `const`. The whole point of `const` is the guarantee that you won't be able to modify the object through that reference. You're not allowed to circumvent `const` by creating a non-`const` reference, so why would it be OK to circumvent `const` by mutating the object's members? – Doval Mar 05 '14 at 14:09
  • +1 :-) Very interesting issues in your update (living with non/const lambdas and maybe weaker problem with external state), I have to digest it longer. Anyway, as for your comment I don't have anything evil in mind -- quite contrary, I am looking for features in order to increase clarity of the code (another one: non-null pointers). Same category here (at least it is my purpose) -- I define `func foo(const String &s)` and this is a signal `s` won't be changed in `foo`. – greenoldman Mar 05 '14 at 16:33
  • 1
    @greenoldman I can definitely see the appeal and rationale. However, I don't think there's a real solution to the problem other than avoiding mutable state as much as possible (along with other nasty things like `null` and inheritance.) – Doval Mar 05 '14 at 17:56
  • "constness is part of a function's signature, so you wouldn't be able to pass non-const functions as arguments to functions that expect const functions." This sentence might need clarification. At least in C++ there are no `const` functions, as applying the concept of `const`-ness to functions seems illogical: What would a "mutable" function modify, its own instructions? Maybe you were thinking of `const` parameters, or so-called `const` member functions (where the `const` denotes the `const`-ness of `this`)? – hoffmale Jan 21 '20 at 16:16
8

The main problem is programmers tend not to use it enough, so when they hit a place where it's required, or a maintainer later wants to fix the const-correctness, there is a huge ripple effect. You can still write perfectly good immutable code without const, your compiler just won't enforce it, and will have a more difficult time optimizing for it. Some people prefer their compiler not help them. I don't understand those people, but there are a lot of them.

In functional languages, pretty much everything is const, and being mutable is the rare exception, if it's allowed at all. This has several advantages, such as easier concurrency and easier reasoning about shared state, but takes some getting used to if you are coming from a language with a mutable culture. In other words, not wanting const is a people issue, not a technical one.

Karl Bielefeldt
  • 146,727
  • 38
  • 279
  • 479
  • +1 thank you. While immutable object is something else than I am looking for (you can make object immutable in C# or Java), even with immutable approach you have to decide if it is deep or shallow, take for example the container of pointers/references (can you change **the value** of objects being point to). After a day it seems the major problem is rather what is set as default, and it is easily solvable when designing new language. – greenoldman Mar 05 '14 at 14:01
  • 1
    +1 for the lolz @ "Some people prefer their compiler not help them. I don't understand those people, but there are a lot of them." – Thomas Eding Mar 05 '14 at 22:18
7

I see the drawbacks as:

  • "const poisoning" is a problem when one declaration of const can force a previous or following declaration to use const, and that can cause its previous of following declarations to use const, etc. And if the const poisoning flows into a class in a library that you don't have control over, then you can get stuck in a bad place.

  • it's not an absolute guarantee. There are usually cases where the const only protects the reference, but is unable to protect the underlying values for one reason or another. Even in C, there are edge cases that const can't get to, which weakens the promise that const provides.

  • in general, the sentiment of the industry seems to be moving away from strong compile-time guarantees, however much comfort they may bring you and I. So for the afternoon I spend touching 66% of my code base because of const poisoning, some Python guy has banged out 10 perfectly workable, well-designed, well-tested, fully functional Python modules. And he wasn't even hacking when he did it.

So maybe the question is, why carry forward a potentially controversial feature that even some experts are ambivalent about when you're trying to gain adoption of your spiffy new language?

EDIT: short answer, a new language has a steep hill to climb for adoption. It's designers really need to think hard about what features it needs to gain adoption. Take Go, for example. Google sort of brutally truncated some of the deepest religious battles by making some Conan-the-Barbarian style design choices, and I actually think the language is better for it, from what I've seen.

sea-rob
  • 6,841
  • 1
  • 24
  • 47
  • 2
    Your second bullet is not correct. In C++, there are three/four ways to declare a reference/pointer, wrt. constness: `int *` mutable pointer to mutable value, `int const *` mutable pointer to const value, `int * const` const pointer to mutable value, `int const * const` const pointer to const value. – phresnel Mar 05 '14 at 07:38
  • Thank you. I asked about the nature, not the marketing ("industry moving away" is not a nature of C++-like `const`), thus such "reasons" I consider irrelevant (for this question at least). About `const` poisoning -- can you give example? Because AFAIK it is the advantage, you pass something `const` and it should remain `const`. – greenoldman Mar 05 '14 at 07:38
  • the latter: lol wait until you spend an afternoon walking though your code base trying to get your damn code to compile again, just because you added a single "const" -- I've been there. The former: as finiky, religious, and compulsive as the programming community is, gaining adoption of a language is a much, much tougher road than getting people to buy your detergent ;) You're swimming in very deep theoretical waters, as well as a typhoon of subjective values. It's hard in this industry to get people to see past their blinders. – sea-rob Mar 05 '14 at 07:48
  • 1
    It is not the `const` problem but changing the type really -- whatever you do, it will always be a problem. Consider passing `RichString`, and then realizing you better pass `String`. – greenoldman Mar 05 '14 at 07:51
  • 4
    @RobY: I am not sure what/whom you are addressing with _latter_ and _former_. However: After that afternoon walking through the code, you should have learned you should refactor to const-correctness bottom-up, not top-down, i.e. start in the innermost units, and work your way up. Maybe your units were also not isolated enough, instead being tightly coupled, or you've fallen prey of inner systems. – phresnel Mar 05 '14 at 08:06
  • @phresnel what about the case in C where you have a const reference and a non-const reference pointing to the same location? The const can't guarantee that the data won't change. Plus you can cast it away (is that right? Been a while). The point is, there are ways to work around it that weaken the constraint & call its value into question. Here's an example article: http://www.airs.com/blog/archives/428 – sea-rob Mar 05 '14 at 08:20
  • @RobY: You have to trust the API documentation and use `const_cast`, iff it tells you it won't update the object (for that exact reason, `const_cast` was introduced). `const_cast`ing just for fun and modifying the object then leads to undefined behaviour, if applied upon an object that was declared const. If you cannot trust an API, or if you have a policy of forbidding `const_cast` altogether, introduce local copies. That's another reason for having nicely separated units and working bottom-up. – phresnel Mar 05 '14 at 08:27
  • @RobY: I realise you were not talking about mixing C and C++, but just C in itself. I am afraid I have not enough knowledge about C const to debate with you :) – phresnel Mar 05 '14 at 08:30
  • ...or I can just use Python, which kind of goes back to the original question of "why doesn't language X adopt (potentially annoying, religious, and problematic) feature Y" – sea-rob Mar 05 '14 at 08:30
  • @phresnel I'm impressed by your knowledge, actually. – sea-rob Mar 05 '14 at 08:31
  • @RobY: I love both systems, actually. Python and C++ are too different in their idioms, I love both. I also love the Haskell way. I have good arguments for and against all systems, but this would blow this comment section. // after reading your comment: Imagine my face red coloured :D Yours, however, is impressive, too. – phresnel Mar 05 '14 at 08:32
  • @phresnel my limited exposure to Haskell has been the most humbling experience of my career. I hate to admit, but I have this secret love of javascript, too. But I don't say that out loud. You should frame your arguments as questions & post, to get the ball rolling :) – sea-rob Mar 05 '14 at 08:35
  • @RobY: Lol :) I actually only recently began introducing JavaScript and some of those frameworks into my toolbox to pimp up my portfolio. It actually came to my surprise I didn't dislike it, and that I even liked it. Prototyping really makes for, well, rapid prototyping; as does weak typing and such :D – phresnel Mar 05 '14 at 09:36
  • 7
    "Const poisoning" isn't really a problem - the real problem is that C++ defaults to non-`const`. It's too easy to not `const` things that should be `const` because you have to opt into it instead of out of it. – Doval Mar 05 '14 at 12:44
  • I completely agree with Doval. "Const poisoning" is not a problem, as long as you always mark functions const when they really are const. And if an external library says a function your're calling is not const, then you can no longer guarantee constness of your function and can not mark it as const, simple as that. – martiert Mar 05 '14 at 13:48
  • The root problem is inconsistent identification of mutable and immutable. If you're on a team that can't agree, or inherit a body of code that disagrees with you, or you just change your mind, then the compiler can create a bigger problem than you bargained for. – sea-rob Mar 05 '14 at 16:15
0

There are some philosophical problems in adding const to a language. If you declare const to a class, that means that the class cannot change. But a class is a set of variables coupled with methods (or mutators). We achieve abstraction by hiding the state of a class behind a set of methods. If a class is designed to hide state, then you need mutators to change that state from the outside and then it isn't const anymore. So it is only possible to declare const to classes that are immutable. So const seem only possible in scenarios where the classes (or the types you want to declare const) are trivial...

So do we need const anyway? I dont think so. I think that you can easily do without const if you have a good design. What data do you need and where, which methods are data-to-data methods and which abstractions do you need for I/O, network, view etc. Then you naturally split modules and classes arise that deal with state and you have methods and immutable classes that don't need state.

I think most problems arise with big fat data-objects that can save, transform and draw itself and then you want to pass it to a renderer for example. So you cannot copy the contents of the data, because that is too expensive for a big data-object. But you dont want it to change either, so you need something like const you think. Let the compiler figure out your design flaws. However if you split those objects in only an immutable data-object and implement the methods in the responsible module, you can pass around (only) the pointer and do things you want to do with the data while also guaranteeing immutability.

I know that it is possible in C++ to declare some methods const and to not declare on other methods, because they are mutators. And then some time later you need a cache and then a getter mutates an object (because it lazy loads) and it isnt const anymore. But that was the whole point of this abstraction right? You don't know what state gets mutated with each call.

Waster
  • 29
  • 2
  • 1
    "So const seem only possible in scenarios where the classes (or the types you want to declare const) are trivial..." [Persistent data structures](http://en.wikipedia.org/wiki/Persistent_data_structure) are immutable and non-trivial. You just won't see them in C++ because pretty much all of them share their internal data between multiple instances, and C++ lacks garbage collection to make that work. "I think that you can easily do without const if you have a good design." Immutable values make code a lot simpler to reason about. – Doval Mar 05 '14 at 12:40
  • +1 thank you. Could you please clarify on "declare const to a class"? Did you mean setting class as a const? If yes, I am not asking about this -- C++ const works like a view, you can declare a const next to object, not a class (or something changed in C++11). Also there is a problem with the next paragraph and word "easily" -- there is a good question about C++-const in C# and the amount of work it involves is paramount -- for every type that you intend to put `const` (in C++ sense) you have to define interface, also for all properties. – greenoldman Mar 05 '14 at 12:46
  • 1
    When you're talking about a "class" you actually mean "instance of an class", right? I think that's an important distinction. – svick Mar 05 '14 at 12:51