<p>Would it be safe to introduce the <code>freeze</code> statement for freezing a mutable object into an immutable object?</p>
<p>Such statement would mix well into dynamically-typed languages such as Python or JavaScript. Let me demonstrate some scenarios.</p>
<p>The most basic scenario is when I want an object to be mutable only before some point. Freezing the object afterwards would prevent unexpected runtime bugs. (JS-esque pseudocode)</p>
<pre class="lang-js prettyprint-override"><code>let foo = 0;
// Some operations involving foo
freeze foo;
// Now foo is immutable; every attempt to rebind foo shall be an error
</code></pre>
<p>You might argue that I could write <code>const bar = foo;</code> and work with <code>bar</code> afterwards, but still, this <code>freeze</code> statement can act as a syntactic sugar.</p>
<p>Furthermore, there are scenarios that only <code>freeze</code> can achieve. An example scenario is when a condition is choosing which object to freeze. (JS-esque pseudocode)</p>
<pre class="lang-js prettyprint-override"><code>let foo = 0;
let bar = 1;
// Some operations involving foo and bar
const cond = /* A boolean value resulted from the operations */;
freeze cond ? foo : bar;
</code></pre>
<p>Another example scenario is when the object to be frozen is in a container. (Python-esque pseudocode):</p>
<pre class="lang-py prettyprint-override"><code>foo = []
# Some operations involving foo
for item in foo:
    freeze item
# Now objects contained by foo are frozen, but foo itself can be modified
</code></pre>
<p>So why aren't languages doing this? Are there unexpected caveats I didn't notice?</p>

<immutable><statements><mutability>
Would it be safe to introduce the "freeze" statement?
CC BY-SA 4.0
2024-02-14T09:35:49.763Z
I'm not sure what practical purpose it would serve to be able to dynamically choose what object to freeze. It doesn't make sense to mutate something that may or may not be mutable so the programmer would still not be able to mutate either option.
mousetail 'he-him'
CC BY-SA 4.0
2024-02-14T09:37:20.947Z
Note that [Object.freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) is already a function on Javascript, though it works on objects not variables. Freezing variables seems pointless however
mousetail 'he-him'
CC BY-SA 4.0
2024-02-14T10:13:15.373Z
In your scenario with the conditional, what would be a use-case for having two variables where one of them is frozen but the other is not? If the programmer tries to mutate a frozen variable, is that an error or does nothing happen ?
Tom Tsagkatos
CC BY-SA 4.0
2024-02-14T18:20:32.417Z
Your question conflates mutability of objects, with rebindability of names. These are different things; note how e.g. in JavaScript `const foo = [];` prevents rebinding like `foo = [1, 2, 3];` but doesn't prevent mutating the array like `foo.push(5);`. In contrast, `let bar = Object.freeze([]);` is the opposite in both respects.
kaya3
CC BY-SA 4.0
2024-02-14T18:24:47.147Z
It's also not clear what you mean by "safe" in the title of your question. Do you mean type safety, memory safety, or something else?
kaya3
CC BY-SA 4.0
2024-02-14T19:47:53.483Z
What happens when something attempts to mutate a frozen object?  There are two obvious answers: throw a runtime error or do nothing.  I consider both to be unexpected runtime errors.
JimmyJames
CC BY-SA 4.0
2024-02-14T23:47:42.703Z
@TomTsagkatos: If code tries to duplicate an object which holds a valid frozen object representing its current state, the duplicate object can be given a reference to that frozen object; otherwise a new frozen object can be constructed, cached, and used to make the new object.  If code tries to mutate an object which doesn't hold a valid mutable object for its state, it can make a mutable copy of the frozen object and invalidate the frozen object.
supercat
CC BY-SA 4.0
2024-02-15T10:57:35.633Z
"why aren't languages doing this?" – Do you mean something like [ECMAScript](https://tc39.es/)'s [`Object.freeze`](https://tc39.es/ecma262/#sec-object.freeze)? Or something like [Ruby](https://ruby-lang.org/)'s [`Object#freeze`](https://docs.ruby-lang.org/en/master/Object.html#method-i-freeze)? How does your proposal differ from each of those?
Jörg W Mittag
<p>An important question which is often overlooked in language design is whether a variable or field of a container type should be treated as encapsulating the <em>identity</em> of a container, its <em>contents</em>, or in some rare cases, both.  There are generally two ways a variable or field (which I'll collectively, &quot;entity&quot;) may encapsulate a container's contents:</p>
<ol>
<li><p>The entity may hold the only reference in the universe to a mutable container.</p>
</li>
<li><p>The entity may hold a shareable reference to an immutable container.</p>
</li>
</ol>
<p>An action which makes a container immutable, but has no other effect, would not be as useful as one which, given either of the above, will yield a shareable reference to an immutable container having the same content.  Such action could be accomplished in any of three ways:</p>
<ol>
<li><p>Return the original reference, if it was already a shareable reference to an immutable container.</p>
</li>
<li><p>Produce a new immutable container with contents copied from the original.</p>
</li>
<li><p>Make the original container immutable, and return a reference to it.</p>
</li>
</ol>
<p>Scenario #3 would only be safe in situations where an implementation could guarantee that no references to the original could exist--possibly in other threads--in ways that would be usable to mutate the container.  If a language has a distinct &quot;non-shareable reference&quot; type, it could support a construct to create a mutable object and a non-shareable reference to it, pass that reference to some code that could use it but not persist it, freeze the object, and then yield a shareable reference.  I'm not sure what syntax would be best for this, but it would not take the form of a stand-alone &quot;freeze&quot; command.  If a language doesn't have a means of representing non-shareable references, a facility to have one type definition create a mutable container type, an immutable container type, and virtual functions which can be used on either type to create shareable immutable clones or non-shareable mutable ones, would also probably be safer and more useful than a &quot;freeze&quot;.</p>

CC BY-SA 4.0
2024-02-15T19:03:26.650Z
2024-02-15T19:03:26.650Z
supercat
CC BY-SA 4.0
2024-02-16T22:46:38.853Z
Immediately thought "what happens to existing non-const aliases" and this answer was the only one to recognize the problem.
Ben Voigt
CC BY-SA 4.0
2024-02-16T23:00:07.047Z
@BenVoigt: It's very easy for a language like Java to treat all references the same, but doing that makes it hard to avoid copying objects that will never end up being mutated, or constructing new immutable versions of objects which are about to be discarded.  Producing a new "edited" immutable object by making a mutable copy of an object, applying required changes, and freezing it, is much cleaner and easier than trying to having to manually specify every attribute which is supposed to be passed through unchanged, but if the language doesn't recognize "unshareable references"...
supercat
CC BY-SA 4.0
2024-02-16T23:02:07.510Z
...it's hard to guard against non-const aliases.  Further, while I didn't mention it, recognizing different kinds of references makes it possible for a language to meaningfully supply equality-check and cloning methods, though two of each should be specified, to handle the kinds of references that might be used to identify an object.
supercat
CC BY-SA 4.0
2024-02-17T02:31:34.780Z
What about shared references to mutable containers (and unshared references to immutable containers, though those are boring)?
Bergi
CC BY-SA 4.0
2024-02-17T04:11:07.033Z
@Bergi: Mutable objectrs to which shared references exist generally have one "owning" entity, whose state encapsulates the state of the object, and other entities which either observe the state, or put data into the entity for the benefit of its owner.  In some cases, it may make sense for the owning entity to freeze an entity, but owning vs. non-owning entities add issues which go beyond freezing.
supercat
<p>The basic idea that it should be possible to lock data from modification is sound.</p>
<p>It is often the case that data structures have to undergo a progressive building process, during which time elements may be modified freely and the whole structure passes through a series of intermediate states, but after which there should then be constraints on modification.</p>
<p>One problem with the <code>freeze</code> idea though, is that in real application designs, underlying constraints on modification are often more fine-grained than applying to a whole structure at once, and the shape of the constraints may vary more than once during the lifetime of a particular structure. It is also often the case that only certain values or state transitions are constrained, rather than a simple dilemma of whether to allow all modification of a single field or allow none.</p>
<p>Secondly, the idea proceeds from the wrong assumption that programmers mostly just forget about the existence of specific and very simple constraints.</p>
<p>In fact, most errors arise because programmers either become confused at the analysis stage about the constraints which <em>ought</em> to apply (that is, they become confused about specifying what should be frozen and when), or having devised a set of constraints that are non-trivial and dynamical in their shape, and which are too complicated to be enforced by the compiler, they become confused about their context and therefore proceed to program in violation of those constraints.</p>
<p>Programming language solutions that solve only the simple cases of a known problem, whilst leaving the programmer still to wrestle alone with the most complicated cases, are often highly pernicious, since they often introduce only additional fuss and complexity around the simple cases which the programmer could already manage without support, but which fuss and complexity then causes the complex cases to be starved of mental resources when those were already cases with which the programmer was struggling to cope.</p>
<p>It's also very common that any kind of constraint on the mutability of existing structures, but with no constraint on the construction of new structures, just leads to a proliferation of &quot;snakeskin-shedding&quot; where essentially the same data goes through a mutation process by way of constant reconstruction of the structures which hold it. This introduces additional complexity in the code, compared to direct mutation of an existing structure, but rarely has any benefit like what it's proponents imagine to offset the hassle.</p>

CC BY-SA 4.0
2024-02-15T03:49:15.530Z
2024-02-15T06:35:41.280Z
2024-02-15T06:35:41.280Z
Steve
<h2>Introduce a mutation qualifier for entire objects or individual fields</h2>
<p>In static languages this would be unwise, as this would introduce an error similar to <code>NullPointerException</code> in basically <em>any</em> part of code. Newer languages are moving away from these types of runtime errors.</p>
<p>Instead of introducing a <code>frozen</code> statement, it would be better to default all objects as non-mutating, init only, basic types <em>and user composite types</em>, and optionally marking any parameter as being <code>mut</code> in functions that mutating the object is necessary or expected.</p>
<p>The reason for this is that mutating objects are few and short lived, as they would be transformed in non-mutating versions soon, or that eternal mutating objects should be few anyway.</p>
<pre><code>class List      // Immutable version
{ }

class List mut  // Mutating version
{
    public List operator mut(List mut)
    {} // Manual conversion to immutable, may be auto generated
}

def Add( mut list List , T elem ) mut List
{ } // Add can only be defined for mutating lists

def GetEnumerator( mut coll Collection ) UnstableEnumerator
{ } // UnstableEnumerator can throw

def GetEnumerator ( coll Collection ) StableEnumerator
{ } // Stable enumerator is notrow!

mut var list = List(); // Will pick the mutable version
list.Add( 1 ).Add( 2 ).Add( 4 );

var yogurt = list; // mut var to var, call mut/const operator

yogurt.Add( 8 );  // Compile error

def UserDefinedOnlyTraverse( list List )
{
    list[0] *= 2; // Compile error
}
</code></pre>
<p>This is a form of mutability overloading. That is, to use the same type name for mutable and immutable versions. But this would need support from language to annotate parameters, properties and variables to choose or restrict that version is to be used.</p>

CC BY-SA 4.0
2024-02-14T13:05:00.527Z
2024-02-15T12:36:05.207Z
2024-02-15T12:36:05.207Z
André L F S Bacci
<p>In mousetail's accepted answer, they mention that there are two major ways of thinking about freezig.  You can freeze a variable or you can freeze the data.</p>
<p>If you're thinking about freezing a variable, it's worth looking into all of the nuances of C++'s <code>const</code> keyword.  They indeed freeze a variable, and there's even a <code>const_cast</code> that lets you &quot;thaw&quot; a variable too.  There's quite a lot of content on just how complicated <code>const</code> is, and <code>const</code> doesn't even provide guarantees, just convenience.  If I have a <code>const</code> variable, that doesn't make it immutable, it just means I am not permitted to mutate it.  Another function with a non-const reference to the same data can change it.  I expect any &quot;freeze&quot; keyword you construct which acts on variables will have at least as many nuances as <code>const</code> did, and likely have even more.</p>
<p>Applying &quot;freeze&quot; to values has been done in many languages.  I've even written one myself with that functionality.  It's a tricky thing though.  It's typically extremely hard to follow frozen-ness through a Turing complete language, so you have to do some extra effort at runtime.  Typically this either means having a flag on every object that might be not-frozen which is checked on every mutating operation, or it means copying the data from one datastructure to another.</p>
<p>One major concern is whether there is only one concept of &quot;frozen&quot; which makes sense in your language.  C++ ran into this one with <code>const</code>, and it's sibling, <code>mutable</code>.  One camp of programmers wanted <code>const</code> to mean &quot;does not change any memory,&quot; and the other wanted it to mean &quot;effectively does not change the state of an object.&quot;  The latter would permit effects like caching which don't change the result of operations but may change the runtime cost of them.  This was implemented in the language via the <code>mutable</code> keyword.  This debate rages today.  In fact, it has even more significance now, because the language now has support for threads built into the language.  &quot;Does not change any memory&quot; can easily be shown to be threadsafe in lots of circumstances, while &quot;effectively does not change the state&quot; can introduce subtle race cases.</p>
<p>In other words, if you give a mouse a cookie, they'll probably ask for some milk.  If you give them frozen, they'll probably ask for a half dozen other related semantics.</p>

CC BY-SA 4.0
2024-03-13T14:19:35.373Z
2024-03-13T14:19:35.373Z
Cort Ammon
<p>The feature you describe would do two different things, both making the value of a variable immutable, and also prevent reassignment to a variable. Both of those things are individually already possible and combining them would create a whole lot of messy edge cases.</p>
<h2>Immutable Variables</h2>
<p>Both options are already possible. As you note, you can just rebind a <code>let</code> variable as <code>const</code> in Javascript:</p>
<pre><code>let a=[];
const b=a;
// but note you can still change the value
b.push(12);
</code></pre>
<p>Python has no feature for immutable variables. You could argue it would benefit from such a feature.</p>
<h2>Immutable Values</h2>
<p>In Javascript you can create immutable values using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze" rel="noreferrer"><code>Object.freeze(...)</code></a>:</p>
<pre><code>let k = Object.freeze({&quot;self&quot;: {&quot;king&quot;: &quot;henry&quot;}});
k.baz = 15; // errors in strict mode

let m = k;
m.foo = &quot;bar&quot;; // also errors since it points to the same object

k.self.king = &quot;Charles&quot;; // fine, only the top level is protected

k = []; // fine
</code></pre>
<p><em>Outside of strict mode any attempt to modify will just do nothing but give no errors in JS</em></p>
<p>Python also has various mechanisms to prevent modifications, like tuples, frozensets, frozen dataclasses, named tuples, etc. They work in the same way.</p>
<h2>Why combining the two is a bad idea</h2>
<h3>Convenience</h3>
<p>First of all it would just be less convenient than the existing options.</p>
<pre class="lang-js prettyprint-override"><code>const a = Object.freeze({...});
</code></pre>
<p>Is 1 line shorter than</p>
<pre class="lang-js prettyprint-override"><code>let a = {...};
freeze a;
</code></pre>
<h3>Recursion</h3>
<p>You don't specify if <code>freeze</code> should act recursively or just prevent modifying the top level value. Both seem bad.</p>
<ul>
<li>If you don't recurse, you'd need to individually freeze every property of a large object, adding to the inconvenience above. With <code>Object.freeze</code> at least you can do it inline</li>
<li>If you do recurse, you couldn't freeze anything that had a reference to something that you didn't want frozen. Does your object contain a reference to some HTML element? Well now your entire <code>window</code> object is frozen and you can't do anything anymore. Essentially it would effect things way outside the &quot;scope&quot; of the current statement leading to large scale unexpected effects.</li>
</ul>
<h3>Freezing expressions</h3>
<p>If you run:</p>
<pre class="lang-js prettyprint-override"><code>function k() { return [5,3,2] };
freeze k();
</code></pre>
<p>What should happen?</p>
<p>There seems to be a very limited type of expressions where your <code>freeze</code> would actually make sense, leading to a lot of special casing. Other similar operators like <code>delete</code> work properly for all expressions.</p>
<h3>Conditionally freezing variables</h3>
<p>You mention this code in your example:</p>
<pre class="lang-js prettyprint-override"><code>let foo = 0;
let bar = 1;
// Some operations involving foo and bar
const cond = /* A boolean value resulted from the operations */;
freeze cond ? foo : bar;
</code></pre>
<p>However, immutability is just a tool to help a programmer avoid mistakes. It is still the programmers responsibility to avoid mutating the variable or the program will crash. So if you want to avoid crashes, you don't mutable even a possibly immutable object. Thus this would effectively just prevent the programmer from mutating both <code>foo</code> and <code>bar</code> so you might as well freeze both.</p>
<p>If you later branch and do either something with <code>foo</code> or with <code>bar</code> you might as well move the re-declaration inside the condition.</p>

CC BY-SA 4.0
2024-02-14T10:17:06.443Z
2024-02-17T13:01:16.197Z
2024-02-17T13:01:16.197Z
mousetail 'he-him'
CC BY-SA 4.0
2024-02-15T02:36:33.327Z
Regarding Python, it has no runtime feature for immutable variables, but there is `Final` for type checkers to verify functional immutability ahead of time. And in my experience, that is usually good enough unless you’re computing variable names at runtime given that there is no way in the language to leverage write-once semantics for a variable (as compared to a language like Erlang).
Austin Hemmelgarn
CC BY-SA 4.0
2024-02-15T23:09:07.177Z
Regarding the last part, I can imagine some algorithm along the lines of DFS where you conditionally freeze e.g. a node in a graph after visiting it, as a way of asserting that the algorithm won't visit (and mutate) that node again. Or for freezing names as opposed to objects, consider a merge function with two index variables; you have a loop which on each iteration increments one or the other variable, and then you freeze that variable if it has reached its limit, to assert that it won't be incremented again.
kaya3
CC BY-SA 4.0
2024-02-17T11:36:10.570Z
`k.foo = "bar";` do you mean `m.foo = "bar"`?
njzk2
CC BY-SA 4.0
2024-02-17T16:08:54.810Z
They could also use this syntax (with the `freeze` keyword): `const obj = freeze {...}`, maybe to help distinguish itself from JavaScript.
Timotej Leginus
<p>This already exists in Haskell's <a href="https://hackage.haskell.org/package/vector" rel="nofollow noreferrer"><code>vector</code></a> and <a href="https://hackage.haskell.org/package/array" rel="nofollow noreferrer"><code>array</code></a> packages. In fact, they not only have <code>freeze</code>, they also have <code>thaw</code>.</p>
<p>Taking <code>vector</code> as an example, there are two types involved: one for immutable vectors and one for mutable vectors. You can only perform mutations on the mutable vectors inside <code>IO</code> or <code>ST</code>. And, as you'd expect, mutations are not possible on <em>immutable</em> vectors.</p>
<p>So, for instance, maybe you want to implement some algorithm using mutation to modify an vector. But later you want to use this vector in pure code. Maybe you get the original vector <em>from</em> pure code as well. You could write:</p>
<pre><code>import Data.Vector as V
import Data.Vector.Mutable

inPlaceAlgorithm :: IOVector Int -&gt; IO ()
inPlaceAlgorithm v = ...

buildVector :: Int -&gt; Vector Int
buildVector n = ...

timesTwo :: Vector Int -&gt; Vector Int
timesTwo = V.map (*2)

main :: IO ()
main = do
  let immutableVec = buildVector 20

  mutableVec &lt;- thaw immutableVec
  inPlaceAlgorithm mutableVec

  newImmutableVec &lt;- freeze mutableVec
  print (timesTwo newImmutableVec)
</code></pre>
<p>In this particular situation, the answer to your question is: Yes, <code>freeze</code> is safe (and, in fact, so is <code>thaw</code>). This answer might be different in a different situation, though, depending on the specifics of the language.</p>
<p>This is slightly different than what you describe because it is making copies. There is an unsafe version that does not make a copy. You could probably avoid the copying while maintaining safety by building <code>freeze</code> into the language and then doing a static check to see if the variable is ever mutated after it's frozen. I don't think this would be less safe than the safe Haskell <code>freeze</code>.</p>
<p>However, if you do that you will run into an issue with the conditional freezing you mention: you'll have to treat both choices as frozen at compile-time. This problem seems unavoidable for conditional freezing.</p>
<p>Though you specifically refer to dynamically typed languages, the static type system of Haskell is actually the main thing that is <em>making</em> this safe (in this situation).</p>
<h1>Showing correctness</h1>
<p>In a statically typed setting, here is one approach to proving that an approach to freezing is safe:</p>
<ol>
<li>Define the syntax and <a href="https://langdev.stackexchange.com/a/2693/172">typing rules</a> for the language, including <code>freeze</code></li>
<li>Define <a href="https://langdev.stackexchange.com/questions/3352/how-can-i-specify-a-programming-language-step-by-step-more-formally-than-by-prov/3353#3353">an operational semantics</a> for the language, being careful to not include any rules that would involve mutating something that's frozen</li>
<li>Prove that the operational semantics really never results in something immutable being mutated</li>
<li>Prove <a href="https://langdev.stackexchange.com/questions/3372/what-does-it-mean-for-a-type-system-or-language-to-be-sound/3377#3377">type soundness</a></li>
</ol>
<p>If the operational semantics does not include a rule for mutating something that's frozen, then type soundness implies that immutable things will never be mutated due to undefined behavior (in particular due to the progress theorem).</p>

CC BY-SA 4.0
2024-02-14T17:25:58.900Z
2024-02-15T00:56:57.087Z
2024-02-15T00:56:57.087Z
David Young
# Would it be safe to introduce the "freeze" statement?
Posted by: username_0 on 2024-02-14
Would it be safe to introduce the freeze statement for freezing a mutable object into an immutable object?
Such statement would mix well into dynamically-typed languages such as Python or JavaScript. Let me demonstrate some scenarios.
The most basic scenario is when I want an object to be mutable only before some point. Freezing the object afterwards would prevent unexpected runtime bugs. (JS-esque pseudocode)
let foo = 0;
// Some operations involving foo
freeze foo;
// Now foo is immutable; every attempt to rebind foo shall be an error

You might argue that I could write const bar = foo; and work with bar afterwards, but still, this freeze statement can act as a syntactic sugar.
Furthermore, there are scenarios that only freeze can achieve. An example scenario is when a condition is choosing which object to freeze. (JS-esque pseudocode)
let foo = 0;
let bar = 1;
// Some operations involving foo and bar
const cond = /* A boolean value resulted from the operations */;
freeze cond ? foo : bar;

Another example scenario is when the object to be frozen is in a container. (Python-esque pseudocode):
foo = []
# Some operations involving foo
for item in foo:
    freeze item
# Now objects contained by foo are frozen, but foo itself can be modified

So why aren't languages doing this? Are there unexpected caveats I didn't notice?
**Tags:** immutable statements mutability
> **Comments:**
> **username_1**: I'm not sure what practical purpose it would serve to be able to dynamically choose what object to freeze. It doesn't make sense to mutate something that may or may not be mutable so the programmer would still not be able to mutate either option.  
> **username_1**: Note that [Object.freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) is already a function on Javascript, though it works on objects not variables. Freezing variables seems pointless however  
> **username_2**: In your scenario with the conditional, what would be a use-case for having two variables where one of them is frozen but the other is not? If the programmer tries to mutate a frozen variable, is that an error or does nothing happen ?  
> **username_3**: Your question conflates mutability of objects, with rebindability of names. These are different things; note how e.g. in JavaScript `const foo = [];` prevents rebinding like `foo = [1, 2, 3];` but doesn't prevent mutating the array like `foo.push(5);`. In contrast, `let bar = Object.freeze([]);` is the opposite in both respects.  
> **username_3**: It's also not clear what you mean by "safe" in the title of your question. Do you mean type safety, memory safety, or something else?  
> **username_4**: What happens when something attempts to mutate a frozen object?  There are two obvious answers: throw a runtime error or do nothing.  I consider both to be unexpected runtime errors.  
> **username_5**: @TomTsagkatos: If code tries to duplicate an object which holds a valid frozen object representing its current state, the duplicate object can be given a reference to that frozen object; otherwise a new frozen object can be constructed, cached, and used to make the new object.  If code tries to mutate an object which doesn't hold a valid mutable object for its state, it can make a mutable copy of the frozen object and invalidate the frozen object.  
> **username_6**: "why aren't languages doing this?" – Do you mean something like [ECMAScript](https://tc39.es/)'s [`Object.freeze`](https://tc39.es/ecma262/#sec-object.freeze)? Or something like [Ruby](https://ruby-lang.org/)'s [`Object#freeze`](https://docs.ruby-lang.org/en/master/Object.html#method-i-freeze)? How does your proposal differ from each of those?  
---
## 6 answers
### Answer by username_1 on 2024-02-14. Score: 15 | Accepted answer
The feature you describe would do two different things, both making the value of a variable immutable, and also prevent reassignment to a variable. Both of those things are individually already possible and combining them would create a whole lot of messy edge cases.
Immutable Variables
Both options are already possible. As you note, you can just rebind a let variable as const in Javascript:
let a=[];
const b=a;
// but note you can still change the value
b.push(12);

Python has no feature for immutable variables. You could argue it would benefit from such a feature.
Immutable Values
In Javascript you can create immutable values using Object.freeze(...):
let k = Object.freeze({"self": {"king": "henry"}});
k.baz = 15; // errors in strict mode

let m = k;
m.foo = "bar"; // also errors since it points to the same object

k.self.king = "Charles"; // fine, only the top level is protected

k = []; // fine

Outside of strict mode any attempt to modify will just do nothing but give no errors in JS
Python also has various mechanisms to prevent modifications, like tuples, frozensets, frozen dataclasses, named tuples, etc. They work in the same way.
Why combining the two is a bad idea
Convenience
First of all it would just be less convenient than the existing options.
const a = Object.freeze({...});

Is 1 line shorter than
let a = {...};
freeze a;

Recursion
You don't specify if freeze should act recursively or just prevent modifying the top level value. Both seem bad.

If you don't recurse, you'd need to individually freeze every property of a large object, adding to the inconvenience above. With Object.freeze at least you can do it inline
If you do recurse, you couldn't freeze anything that had a reference to something that you didn't want frozen. Does your object contain a reference to some HTML element? Well now your entire window object is frozen and you can't do anything anymore. Essentially it would effect things way outside the "scope" of the current statement leading to large scale unexpected effects.

Freezing expressions
If you run:
function k() { return [5,3,2] };
freeze k();

What should happen?
There seems to be a very limited type of expressions where your freeze would actually make sense, leading to a lot of special casing. Other similar operators like delete work properly for all expressions.
Conditionally freezing variables
You mention this code in your example:
let foo = 0;
let bar = 1;
// Some operations involving foo and bar
const cond = /* A boolean value resulted from the operations */;
freeze cond ? foo : bar;

However, immutability is just a tool to help a programmer avoid mistakes. It is still the programmers responsibility to avoid mutating the variable or the program will crash. So if you want to avoid crashes, you don't mutable even a possibly immutable object. Thus this would effectively just prevent the programmer from mutating both foo and bar so you might as well freeze both.
If you later branch and do either something with foo or with bar you might as well move the re-declaration inside the condition.
> **Comments:**
> **username_12** (2024-02-15): Regarding Python, it has no runtime feature for immutable variables, but there is `Final` for type checkers to verify functional immutability ahead of time. And in my experience, that is usually good enough unless you’re computing variable names at runtime given that there is no way in the language to leverage write-once semantics for a variable (as compared to a language like Erlang).  
> **username_3** (2024-02-15): Regarding the last part, I can imagine some algorithm along the lines of DFS where you conditionally freeze e.g. a node in a graph after visiting it, as a way of asserting that the algorithm won't visit (and mutate) that node again. Or for freezing names as opposed to objects, consider a merge function with two index variables; you have a loop which on each iteration increments one or the other variable, and then you freeze that variable if it has reached its limit, to assert that it won't be incremented again.  
> **username_13** (2024-02-17): `k.foo = "bar";` do you mean `m.foo = "bar"`?  
> **username_14** (2024-02-17): They could also use this syntax (with the `freeze` keyword): `const obj = freeze {...}`, maybe to help distinguish itself from JavaScript.  
---
### Answer by username_15 on 2024-02-14. Score: 9
This already exists in Haskell's vector and array packages. In fact, they not only have freeze, they also have thaw.
Taking vector as an example, there are two types involved: one for immutable vectors and one for mutable vectors. You can only perform mutations on the mutable vectors inside IO or ST. And, as you'd expect, mutations are not possible on immutable vectors.
So, for instance, maybe you want to implement some algorithm using mutation to modify an vector. But later you want to use this vector in pure code. Maybe you get the original vector from pure code as well. You could write:
import Data.Vector as V
import Data.Vector.Mutable

inPlaceAlgorithm :: IOVector Int -> IO ()
inPlaceAlgorithm v = ...

buildVector :: Int -> Vector Int
buildVector n = ...

timesTwo :: Vector Int -> Vector Int
timesTwo = V.map (*2)

main :: IO ()
main = do
  let immutableVec = buildVector 20

  mutableVec <- thaw immutableVec
  inPlaceAlgorithm mutableVec

  newImmutableVec <- freeze mutableVec
  print (timesTwo newImmutableVec)

In this particular situation, the answer to your question is: Yes, freeze is safe (and, in fact, so is thaw). This answer might be different in a different situation, though, depending on the specifics of the language.
This is slightly different than what you describe because it is making copies. There is an unsafe version that does not make a copy. You could probably avoid the copying while maintaining safety by building freeze into the language and then doing a static check to see if the variable is ever mutated after it's frozen. I don't think this would be less safe than the safe Haskell freeze.
However, if you do that you will run into an issue with the conditional freezing you mention: you'll have to treat both choices as frozen at compile-time. This problem seems unavoidable for conditional freezing.
Though you specifically refer to dynamically typed languages, the static type system of Haskell is actually the main thing that is making this safe (in this situation).
Showing correctness
In a statically typed setting, here is one approach to proving that an approach to freezing is safe:

Define the syntax and typing rules for the language, including freeze
Define an operational semantics for the language, being careful to not include any rules that would involve mutating something that's frozen
Prove that the operational semantics really never results in something immutable being mutated
Prove type soundness

If the operational semantics does not include a rule for mutating something that's frozen, then type soundness implies that immutable things will never be mutated due to undefined behavior (in particular due to the progress theorem).
---
### Answer by username_5 on 2024-02-15. Score: 6
An important question which is often overlooked in language design is whether a variable or field of a container type should be treated as encapsulating the *identity* of a container, its *contents*, or in some rare cases, both. There are generally two ways a variable or field (which I'll collectively, "entity") may encapsulate a container's contents:

1. The entity may hold the only reference in the universe to a mutable container.
2. The entity may hold a shareable reference to an immutable container.

An action which makes a container immutable, but has no other effect, would not be as useful as one which, given either of the above, will yield a shareable reference to an immutable container having the same content. Such action could be accomplished in any of three ways:

1. Return the original reference, if it was already a shareable reference to an immutable container.
2. Produce a new immutable container with contents copied from the original.
3. Make the original container immutable, and return a reference to it.

Scenario #3 would only be safe in situations where an implementation could guarantee that no references to the original could exist--possibly in other threads--in ways that would be usable to mutate the container. If a language has a distinct "non-shareable reference" type, it could support a construct to create a mutable object and a non-shareable reference to it, pass that reference to some code that could use it but not persist it, freeze the object, and then yield a shareable reference. I'm not sure what syntax would be best for this, but it would not take the form of a stand-alone "freeze" command. If a language doesn't have a means of representing non-shareable references, a facility to have one type definition create a mutable container type, an immutable container type, and virtual functions which can be used on either type to create shareable immutable clones or non-shareable mutable ones, would also probably be safer and more useful than a "freeze".
> **Comments:**
> **username_7** (2024-02-16): Immediately thought "what happens to existing non-const aliases" and this answer was the only one to recognize the problem.  
> **username_5** (2024-02-16): @BenVoigt: It's very easy for a language like Java to treat all references the same, but doing that makes it hard to avoid copying objects that will never end up being mutated, or constructing new immutable versions of objects which are about to be discarded.  Producing a new "edited" immutable object by making a mutable copy of an object, applying required changes, and freezing it, is much cleaner and easier than trying to having to manually specify every attribute which is supposed to be passed through unchanged, but if the language doesn't recognize "unshareable references"...  
> **username_5** (2024-02-16): ...it's hard to guard against non-const aliases.  Further, while I didn't mention it, recognizing different kinds of references makes it possible for a language to meaningfully supply equality-check and cloning methods, though two of each should be specified, to handle the kinds of references that might be used to identify an object.  
> **username_8** (2024-02-17): What about shared references to mutable containers (and unshared references to immutable containers, though those are boring)?  
> **username_5** (2024-02-17): @Bergi: Mutable objectrs to which shared references exist generally have one "owning" entity, whose state encapsulates the state of the object, and other entities which either observe the state, or put data into the entity for the benefit of its owner.  In some cases, it may make sense for the owning entity to freeze an entity, but owning vs. non-owning entities add issues which go beyond freezing.  
---
### Answer by username_9 on 2024-02-15. Score: 5
The basic idea that it should be possible to lock data from modification is sound.

It is often the case that data structures have to undergo a progressive building process, during which time elements may be modified freely and the whole structure passes through a series of intermediate states, but after which there should then be constraints on modification.

One problem with the `freeze` idea though, is that in real application designs, underlying constraints on modification are often more fine-grained than applying to a whole structure at once, and the shape of the constraints may vary more than once during the lifetime of a particular structure. It is also often the case that only certain values or state transitions are constrained, rather than a simple dilemma of whether to allow all modification of a single field or allow none.

Secondly, the idea proceeds from the wrong assumption that programmers mostly just forget about the existence of specific and very simple constraints.

In fact, most errors arise because programmers either become confused at the analysis stage about the constraints which *ought* to apply (that is, they become confused about specifying what should be frozen and when), or having devised a set of constraints that are non-trivial and dynamical in their shape, and which are too complicated to be enforced by the compiler, they become confused about their context and therefore proceed to program in violation of those constraints.

Programming language solutions that solve only the simple cases of a known problem, whilst leaving the programmer still to wrestle alone with the most complicated cases, are often highly pernicious, since they often introduce only additional fuss and complexity around the simple cases which the programmer could already manage without support, but which fuss and complexity then causes the complex cases to be starved of mental resources when those were already cases with which the programmer was struggling to cope.

It's also very common that any kind of constraint on the mutability of existing structures, but with no constraint on the construction of new structures, just leads to a proliferation of "snakeskin-shedding" where essentially the same data goes through a mutation process by way of constant reconstruction of the structures which hold it. This introduces additional complexity in the code, compared to direct mutation of an existing structure, but rarely has any benefit like what it's proponents imagine to offset the hassle.
---
### Answer by username_10 on 2024-02-14. Score: 4
Introduce a mutation qualifier for entire objects or individual fields
In static languages this would be unwise, as this would introduce an error similar to NullPointerException in basically any part of code. Newer languages are moving away from these types of runtime errors.
Instead of introducing a frozen statement, it would be better to default all objects as non-mutating, init only, basic types and user composite types, and optionally marking any parameter as being mut in functions that mutating the object is necessary or expected.
The reason for this is that mutating objects are few and short lived, as they would be transformed in non-mutating versions soon, or that eternal mutating objects should be few anyway.
class List      // Immutable version
{ }

class List mut  // Mutating version
{
    public List operator mut(List mut)
    {} // Manual conversion to immutable, may be auto generated
}

def Add( mut list List , T elem ) mut List
{ } // Add can only be defined for mutating lists

def GetEnumerator( mut coll Collection ) UnstableEnumerator
{ } // UnstableEnumerator can throw

def GetEnumerator ( coll Collection ) StableEnumerator
{ } // Stable enumerator is notrow!

mut var list = List(); // Will pick the mutable version
list.Add( 1 ).Add( 2 ).Add( 4 );

var yogurt = list; // mut var to var, call mut/const operator

yogurt.Add( 8 );  // Compile error

def UserDefinedOnlyTraverse( list List )
{
    list[0] *= 2; // Compile error
}

This is a form of mutability overloading. That is, to use the same type name for mutable and immutable versions. But this would need support from language to annotate parameters, properties and variables to choose or restrict that version is to be used.
---
### Answer by username_11 on 2024-03-13. Score: 1
In mousetail's accepted answer, they mention that there are two major ways of thinking about freezig. You can freeze a variable or you can freeze the data.

If you're thinking about freezing a variable, it's worth looking into all of the nuances of C++'s `const` keyword. They indeed freeze a variable, and there's even a `const_cast` that lets you "thaw" a variable too. There's quite a lot of content on just how complicated `const` is, and `const` doesn't even provide guarantees, just convenience. If I have a `const` variable, that doesn't make it immutable, it just means I am not permitted to mutate it. Another function with a non-const reference to the same data can change it. I expect any "freeze" keyword you construct which acts on variables will have at least as many nuances as `const` did, and likely have even more.

Applying "freeze" to values has been done in many languages. I've even written one myself with that functionality. It's a tricky thing though. It's typically extremely hard to follow frozen-ness through a Turing complete language, so you have to do some extra effort at runtime. Typically this either means having a flag on every object that might be not-frozen which is checked on every mutating operation, or it means copying the data from one datastructure to another.

One major concern is whether there is only one concept of "frozen" which makes sense in your language. C++ ran into this one with `const`, and it's sibling, `mutable`. One camp of programmers wanted `const` to mean "does not change any memory," and the other wanted it to mean "effectively does not change the state of an object." The latter would permit effects like caching which don't change the result of operations but may change the runtime cost of them. This was implemented in the language via the `mutable` keyword. This debate rages today. In fact, it has even more significance now, because the language now has support for threads built into the language. "Does not change any memory" can easily be shown to be threadsafe in lots of circumstances, while "effectively does not change the state" can introduce subtle race cases.

In other words, if you give a mouse a cookie, they'll probably ask for some milk. If you give them frozen, they'll probably ask for a half dozen other related semantics.
---
