How to tell Mojo that something is intended to be constant?
In mojo 24.1.0 (55ec12d6) I get a new warning:
'let' is being removed, please use 'var' insteadI think it is good practice to make explicity my intention that something is constant. How to I do that (in the future) since let is deprecated?
50 Replies
You can use
alias
as a compile time constant. The old let
was there to emphasize immutable variable deceleration which is no longer needed.Of course I know of the existence of alias, it is just that there is a very large semantic difference between
var x = 8
and let x = 8
, namely that it is my intention (the programmer) that a variable (not a compile constant) is constant, how are my colleagues to know my intentions if i cannot make that explicit? What were the reasons for removing this?GitHub
mojo/proposals/remove-let-decls.md at main · modularml/mojo
The Mojo Programming Language. Contribute to modularml/mojo development by creating an account on GitHub.
Congrats @Ehsan M. Kermani, you just advanced to level 1!
I did not comment on the proposal when it was made, but now that it has been executed, I'm interested in the 'official' reason
let
will be removed.
Because now I cannot tell that something is intended to be constant, which is, for me, a very important concept.well, atleast from how I see it, this becomes the same argument as when I proposed private struct fields. The effect you need can be made with changing naming scheme, though the compiler will not recognize it officially. The same way I can say _data is a private field, although not enforced, you can say var DATA is a constant, hence the naming scheme
@benny you mean that by naming a variable with eg capitals we can communicate that it is not intended to be changed, but the compiler will not enforce it (anymore). Similar, that all fields are public, but by naming it differently my colleagues can know that it should not be used.
correct
you can imply behavior without it being enforced, which i believe is intended
i personally am not sure I agree with this, but i see both sides of the arguments
Thus, our ability to write bug free code is sacrificed for ...., i hope it is something important...
A workaround is to create a function. In a function signature, it's possible to declare that an input value can't be mutated. And it works with references too.
👍🏻
along with this, I can think of few cases where I 1. don’t know the value or the function to get the value, and 2. need it to be immutable during runtime. I’m not saying there aren’t any, there are, i’m just saying alias can and should be used wherever possible, and i believe that is what that change is trying to emphasize
plus there are some other issues with let/var being used with pointer/referencd
since semantically you aren’t changing the pointer, rather the value it points to, if given the choice it can be ambiguous for new developers to choose let or var for that use case
whereas alias is a little more imperative that the pointer itself is immutable while its underlying data may not be
^something that reference improves on even more, explicitly saying if it’s a mutable or immutable reference
It introduces complexity to the compiler and it doesn't work well
What started this "bug-free code" movement?
Congrats @toiletsandpaper, you just advanced to level 1!
I want Mojo to do well because it gives us access to top-notch MLIR lowering capabilities (translation: it compiles to speedy code).
For Mojo to succeed, the company Modular is creating a product called Max (written in Mojo) aimed at companies with highly profitable but complicated AI technology setups. And anyone who's dealt with such setups knows what a complete and total nightmare they can be.
So, Modular, help us out (for us programmers' sake). How do we convince others (meaning management, who aren't keen on changing a highly profitable AI stack) that Mojo is a solid language? What does not quite helps is that it lacks basic Encapsulation (no private member fields) and has dropped Immutability. But that's okay if there are solid reasons behind it. Arguments like "it makes the compiler tricky" or "it's tough for new hires to grasp
const int * const foo
" just won't cut it. Even more challenging is how to persuade that Mojo can match Rust's capabilities without these fundamentals.I think every company should use what they feel is the best tool for their job. If all these features are important then Rust is the obvious choice.
For people having problems Mojo is intended to solve, these things are a non-factor. I'm talking AI Researchers, Python developers, and C/C++ devs writing extensions for Python.
How can they be non-factor when pretty much all the foundational AI libraries are written in C++, and C++ devs are trained to use
const
whenever possible? It made the code look horrible (because mutable is the wrong default), but it's the right thing to do nonetheless.@sora Yes, and forgetting a const where it should have been is an error because it signals that something is allowed to be changed, and someday someone will change it, not knowing that it was intended to be immutable. For a compiler this is trivial to check, and absurd that it is dropped from a otherwise sound language.
I would go as far as saying this: even having
let
without the enforcement from the compiler is better than having only var
for it communicates the intent of the programmer better.Yes, agreed. I would have been happier that due to some weird regression Immutability would not have been enforceable anymore, while we kept a well established convention to convey our intent to future programmers.
Lisp
Interesting. I thought Rust
It goes way back. Dijkstra, for instance, does much of his work (proving the correctness of programs) without touching a computer or having a compiler at all. A Discipline of Programming is a must read, if you are interested in "bug-free code".
OCaml, which Rust was originally implemented in and got many of the good ideas from, was invented to implement Coq (a proof assistant). So you can kinda see Rust's lineage. The Software Foundations series is also an interesting read (uses Coq).
I was initially opposed to the removal of
let
, but I've since warmed up to it.
As a language feature, let
basically does nothing. It has no influence on the runtime behaviour of a program. It's essentially just a #comment
on steroids. (Albeit, a useful comment.)
It's weird to have a language feature that does nothing, especially when you have to teach everyone about this nothingness. So I can sympathize with its removal, even if I have historically been a fan of immutability.
As a "fancy comment", let
has two main utilities:
- It allows a programmer to signal that they don't intend for a variable's value to change.
- It allows the compiler to alert the programmer that the program is not consistent with this intention.
There's no particular reason why this intention needs to manifest as a keyword. I suspect 90% of the benefit can be achieved just by using an IDE that colors variables differently depending on whether they are mutated at some point. If you accidentally mutate a variable, its color will change. Most of the time you'll notice this, and stop and think whether you've make a mistake. (Assuming you read your code before committing it!)I think the problem of mutable variables is superficial, Mojo still enforces mutability in the important places. Namely, there can not be more than one owner, and there can't be two immutable borrows.
@Nick!
There's no particular reason why this intention needs to manifest as a keyword. I suspect 90% of the benefit can be achieved just by using an IDE that colors variables differently depending on whether they are mutated at some point.Which brings me back to my question, how do I do that in Mojo? 1. How do I setup a warning that something is changed that should not be changed. 2. And how do I make such warning system explicit (read a new keyword) such that I can read that I made my intent clear. If i cannot do that i will consider "moving on".
I’m not here to convince you to use Mojo, so you’re always free to move on
Immutability is one basic form of proof carrying code. Imagine one day, we can write pre/post conditions in Mojo and have a SMT solver prove the correctness of our code. Given your very narrow view of programming, that also does nothing, it's absurd.
And how is immutability difficult to teach?
Wow, do you really meant to say that?
@Nick! you are just trolling, I want Mojo to be successful, but that is difficult if the basics are dropped without good reasons. If a language can be changed without good reasons, what are we doing here.
Congrats @Henk-Jan Lebbink, you just advanced to level 5!
@sora I just think the "moving on" comment sounded like it was imposing an obligation on me (or somebody else in this thread) to provide a satisfying answer to OP, which is a bit unfair, because I'm just a random person on the internet trying to provide some helpful information.
Immutability is one basic form of proof carrying code.This is not something that needs to be part of the type system though, because it is a trivial property. A local variable is "immutable" iff you didn't mutate it. A keyword doesn't make the lack of mutation any more official
What about enforcing no uninitiated variable? Maybe you don't need or want help from the compiler, but I do. To put it to the extreme, it's "correct code" iff you don't ever make mistake.
A keyword doesn't make the lack of mutation any more officialI think you would agree that compiler does check something for us, so this is not factual.
We and a lot of others are here to invest their personal time (on a Saturday), knowing that someday the need to convince others (read management) that Mojo makes sense, and that is hampered by big decisions that I cannot explain. Thus a nice explanation would be in order.
>What about enforcing no uninitiated variable?
The type system needs to enforce that variables are initialized before use because that is required for soundness. Reading an uninitialized variable at runtime would cause undefined behaviour.
In contrast, reading a variable that has been mutated always yields defined behaviour. So
let
doesn't have the same level of importance/utility as requiring initialization.Sure thing, isn't it "trivial" in the same way if you just don't access them like you don't mutate immutable variable?
No. The compiler needs to enforce initialization because that is required for memory safety. In contrast, immutability is not required to ensure memory safety.
Congrats @Nick!, you just advanced to level 6!
Or put another way: it is always wrong to use an uninitialized variable, but it is basically always correct to use a mutated variable
They are wrong in the sense that they are incorrect code. Memory safety is just a important form of safety.
For a system without unmovable type, moving them would be wrong, e.g.
I don't think we will see eye to eye on this matter. Nice knowing your position, and having this conversation. Let's move on, though.
sure
Now that Immutability checks are moved to some next round of analysis, e.g., "mojo analyze main" which will be allowed to spend hours of analyzing my complicated code littered with "lets". How do we tell such future analyzer that I intended to have something Immutable?
@Henk-Jan Lebbink Regarding your last comments, I am always trying to challenge my beliefs and take on new persectives as I learn new languages and develop as a programmer. Up until a few years ago, I was a major proponent of immutability and whatnot, but now I am more on the fence. Sometimes general principles (e.g. "immutability is better") don't hold up in all situations, so it's worth reflecting on each particular issue and trying to gauge just how important it is for a principle to be upheld in that setting.
At the end of the day, what matters is whether programmers are actually going to be negatively affected in any shape or form by not having a keyword for local variable immutability. And while I agree with most people in this thread that it feels comforting to declare a variable as immutable, it's quite possible that this comforting feeling is just a result of my prior exposure to programming cultures wherein people tended to praise immutability quite heavily.
So personally, I'm going to give Modular the benefit of the doubt (they have a lot of experience with programming languages), and see what a
var
-only world feels like. Will I ever encounter a bug that could have been prevented via a judicious use of let
? I'm not actually sure. But I will find out with experience.Don't get me wrong. I do find you pointing out "it does nothing" interesting. Maybe I've taken immutability for granted for the wrong reasons.
I have some issues with not declaring Immutability when people do not understand what that means (but that is a different problem). So, not using it when it's available is what I (and I suppose Sora) are contemplating. It is worth the experiment. However, Immutability has been a vital tool when sorting out dodgy quality C++ code over the past decade. I'm not sure any of those efforts would have worked without it. In an ideal world where code is clear and easy to understand, Immutability might not make much of a difference to ensuring correctness. But that's not the world I'm stuck in (unfortunately). When working in teams of programmers, I need a means to implement it, and I don't see any other way to enforce it.
I treat Mojo a programming framework for real world problems, and teams of programmers are part of the real world, as is layers of management...
Okay so it aint crystal clear to me yet... but wasn't "let" one of the reasons why mojo was faster and better and more secure than python? And instead of removing it.. why not keep it optional?
The proposal to remove 'let' says no one is aware of any performance implications of the change. You may be thinking of defaulting to passing by immutable reference (borrow) which does have large performance benefits and remains a key feature of the language.
GitHub
mojo/proposals/remove-let-decls.md at main · modularml/mojo
The Mojo Programming Language. Contribute to modularml/mojo development by creating an account on GitHub.
Oh ok thanks
This may be impractical or undesirable, but could a future compiler track mutations made to SCREAMING_SNAKE_CASE variables and emit a warning? (As typically this naming scheme is used for constants in Python)
Ironically, we have that (approx.) at the moment. We can say
If we could switch on/off the semantics of keyword "let" such some don't need to understand what it does, while others can use it because they value their time.
The convention in every language for marking a variable as an unchangeable constant is to use an all-uppercase naming.
It does not require a special language construct but Mojo has aliases
But this constant doesn't seem to be what people are mooning over.
Exactly
If there are two types of variable modifiers that are fully complementary (mutable/immutable) then in theory you should only need one of them to express the intent (overriding the default)
There's obviously reconcilable points here in my opinion, which I hope the Mojo team considers.
- 'let' was not a completely useless keyword to the old Mojo compiler because it was used to AUTO-enforce run-time variable immutability at compile-time (alias is not a replacement).
- 'let' is a meaningless word ('const' would have been a clearer choice), but still, 'var' is already handling the complementary case so I'm glad they are cutting meaningless keywords from the language.
- Immutable variables are important and helpful to reason through code that uses multithreaded or asynchronous processes.
- There probably shouldn't be logic in a compiler to determine if the case used for each variable is all caps - I'm not totally opposed to these kinds of ideas, but really that's just asking for a unnecessarily slow compiler.
Simple solution:
- AUTO-enforce immutable variables by default unless explicitly tacked with 'var' in mojo functions
Seems to me this solves reasonable concerns and provides a good default for making intentions clear in a simple way.