Replies: 28 comments 60 replies
-
Many thanks for this proposal! I was initially against, but there were very good points in the proposal, so I'm in favor of this. It's also closer to Python, and if we wish, we can always take a look at it again once we have lifetimes and references, it will be much easier to understand the implications of bringing back let. I wish there were ways to make some classes/structs immutables, notably when they have a |
Beta Was this translation helpful? Give feedback.
-
The proposal is to remove Over the last few months, I've slowly been working on a counter-proposal where we get rid of both This syntax overlaps with Python's syntax for function-scoped variable declarations (and accordingly, function-scoped name binding), so at first glance it isn't compatible with the lexically-scoped variables that Mojo currently has. However, I think we can resolve this discrepancy. But to achieve that, we have to tweak how names are handled in Mojo. In particular, we have to remove variable shadowing. (But we can replace it with something very similar: name rebinding.) In a That said, it might be worth waiting until Mojo has references/pointers before changing how variable declarations work, because these features are deeply related, and they probably need to be co-designed. Otherwise, the syntax might need to be changed several times in succession.
|
Beta Was this translation helpful? Give feedback.
-
If it doesn't help the compiler much, then let it be gone |
Beta Was this translation helpful? Give feedback.
-
The proposal seems clear and, a good idea. As a student/learner of mojo: In practice, I cannot really see very often using let/var in I would mainly be focusing using those in But again 👍🏽 on the proposal! |
Beta Was this translation helpful? Give feedback.
-
I agree it should be gone, it's a bit disorienting as a Python programmer having to deal with these extra keywords just for variables. I would also argue to at least rename alias as well - Python has a syntax for defining type aliases. |
Beta Was this translation helpful? Give feedback.
-
I am a bit conflicted, maybe because I have a long history of writing Swift, where when declaring named variables I start with a I would say that generally That said I do appreciate that Mojo has it's "roots" in Python and PS: does it mean that every variable would be |
Beta Was this translation helpful? Give feedback.
-
I believe It is also essential that we have a way to prevent the use of This is only my humble opinion and there may also be other solutions to these issues than the |
Beta Was this translation helpful? Give feedback.
-
A few thoughts:
class Person:
name: str
age: int
Person.name # AttributeError (ie name isn't a ClassVar)
Person.__attributes__ == {'name': str} I think re-using this syntax in a local basic block would serve the purpose of local scoping in a way that would feel natural to many python devs. eg quantity: int
if a:
quantity == 0
elif b:
quantity == 1 Both Pyright and Mypy can reason about type narrowing / type refinement in situations similar to this (e.g. starting with |
Beta Was this translation helpful? Give feedback.
-
I'm a bit torn. On one hand, while python doesn't have a concept of a const or immutable local named variable, that doesn't mean the need isn't there. Python is the only language I can think of that doesn't have There's a difference between python lacking the concept of immutability, and pythonistas wishing they had the feature. Long before I heard about functional programming and its preference for immutable variables, many times I wished python had some way to say "I can't reassign the value of this variable". All the more so due to python's lack of lexical scoping and confusing I also have my doubts it's going to be easier teaching that mojo has immutability for references/borrowed values but not for local named variables. Or rather, I think it might be easier for pythonistas. But once you dive into the system's programming aspect of mojo, and especially for people coming from languages like C++, rust, swift, I think it might actually be more confusing (C++ might be easier, since there's an explicit difference between a "const pointer", "pointer to a const", and a "const pointer to a const"). I had to think for a while about what the difference even was, and I'm not even sure my mental model is right. On the other hand, if it makes the code simpler to reason about and is easier for pythonistas to learn, I think that takes a higher priority. In order for mojo to gain a foothold, the "target market" has to be python programmers. Unless a language wants to be niche, I think it has to be able to become (or assimilate with) the dominant leader given by the economic theory of Network Effect. Also |
Beta Was this translation helpful? Give feedback.
-
After thinking a bit about it, I agree |
Beta Was this translation helpful? Give feedback.
-
I agree with you that let shouldn't be used to indicate immutability, but as for the safety and readability benefits of non-reassignable variables, I think the Python community itself would disagree with you. There are multiple ways to indicate a non-reassignable variable in Python:
So clearly, there was enough of a demand for these particular semantics that there are 3 features in the language for them, with two of them being so strict that they produce run-time errors. If Mojo aims to be a superset of Python, why not add support for these features already present in Python? The readability benefits are especially present in situations where a variable is conditionally assigned with complex logic in between its declaration and assignment, rather than the toy example given above. It also prevents footguns like reassigning
I honestly don't know how to balance the need for the above to work properly with the prevention of footguns that come with reassignment. The only idea I have right now is to only allow the reassignment of Edit: Now that the decision has been finalized, at least we can look at other languages that considered this (like Rust) and learn from them. |
Beta Was this translation helpful? Give feedback.
-
I've thought of a situation where not having Mojo is planning to employ copy-on-write for its standard data structures (arrays, strings, dictionaries, etc.) This means that statements of the form Typically, when you're passing around an array of this size, you don't want to be making deep copies willy-nilly. But with copy-on-write data structures, an operation as simple as That said, if your program accidentally mutates |
Beta Was this translation helpful? Give feedback.
-
I really like this proposal. I'm coming from a full-time python background and this proposal allows for a cleaner mental model imo. I like to imagine all variables/data at the most basic level as mutable. Safety and immutability are constraints that can be applied afterwards. If you own a house, you can change it however you like. If you rent a house, you can paint the walls and change things but the deed to the address is not yours. If you book the house for an event you cannot change anything. In all accounts its still just a house that is fundamentally changeable at any moment, but safety and immutability are contracts applied to it. Pushing immutability to 'the world of borrowed arguments and immutable references' seems like a good fit and a natural next step for Mojo-Python. |
Beta Was this translation helpful? Give feedback.
-
-1. I think immutability granted by |
Beta Was this translation helpful? Give feedback.
-
I talked about this in an earlier comment thread, but I'd like to get some feedback on this question. After some more thinking, how can you take an immutable reference to a referent that is mutable? In rust for example, to have an immutable reference, the referent must also be immutable let name = String::from("rarebreed"); // immutable local var
let name_mut_ref = &mut name; // fails to compile. Can't take a mutable ref to an immutable var
let name_ref = &name; // this is fine. name_ref is an immutable reference since name is immutable
let mut name = String::from("rarebreed");
let name_mut_ref = &mut name; // works now, because referent is mutable, so I can take a mutable ref One way I can see how to take an immutable reference from a mutable referent is that the compiler will check that:
This last point is going to be confusing. Because the referent is declared mutable, but you can't mutate it while the other immutable references are still alive. Am I missing some other way to take an immutable reference to a mutable referent? Could the proposal be updated to include how this can be done? |
Beta Was this translation helpful? Give feedback.
-
You could remove the keyword ( var ) Why don't we simplify things? var a: String = "Hello Mojo 🔥" ❌ a: String = "Hello Mojo 🔥" ✅. # Here Mojo Compiler 🔥 will recognize the type of the If you write the variable type as we explained in the previous example, the variable type will be recognized at compile time. If you do not write the variable type, the variable type will be recognized at run time For example a = "Hello Mojo 🔥" ✅ # here Mojo Compiler 🔥 will recognize the type of the variable |
Beta Was this translation helpful? Give feedback.
-
If mutation and reassignment are two things, does it mean we can have an immutable reference that can be reassigned?
|
Beta Was this translation helpful? Give feedback.
-
This is why the more I think about it, I feel like reassignment of local variables and immutable references is not going to be easier to teach for both python programmers and system programmers. Now you have several confusing aspects:
So the more I think about this, I think it's not just a "nice to have" |
Beta Was this translation helpful? Give feedback.
-
No worries at all for the delay, I am sure all of you at Modular are super busy :) I understand Right now, I still don't have a good picture how removing |
Beta Was this translation helpful? Give feedback.
-
Regardless of whether it's a good idea to have immutable bindings in general it's worth noting that adding At such an early stage it's probably better to avoid features like |
Beta Was this translation helpful? Give feedback.
-
In my opinion let should stay in Mojo. Altho my primary language is Python, i do love Swift, and from time to time use Rust when i need the performance that Python can’t achieve (hopping to switch to Mojo for this :) ). My reasons are the following:
struct Meters_convertor:
class Example: I realize that this may be done with using alias, but having this being explicit feels more natural, granular if you will.
|
Beta Was this translation helpful? Give feedback.
-
Thank you all for the feedback on this. At this point, we've decided that it is the right local step to remove However, if you're a fan of |
Beta Was this translation helpful? Give feedback.
-
I know that I'm late here, also I'm not a good English speaker so this will be short. introI think removing behavior (mutable for def functions and immutable for fn functions) in another way, def functions would be no longer full Python-compatible. I think the perfect way to build mojo, is following the Python default behavior2 (at syntax meaning). With every possible compiler optimization. And then add Mojo🔥 syntax to improve it. my thoughtsIn my opinion, we definitely need an initializer specifier for immutable vars in MOJO, since we don't have such of thing in Python. On the other hand, I like I think that a much better idea could be to infer the type of variables without In Python, we have the walrus operator Footnotes |
Beta Was this translation helpful? Give feedback.
-
Personally, I liked the |
Beta Was this translation helpful? Give feedback.
-
I commented about my thoughts on I was initially opposed to the removal of As a language feature, It's weird to have a language feature that does nothing. Furthermore, it's awkward to have to teach new Mojo users about this nothingness, right at the beginning of their learning journey. So I can sympathize with As a "fancy comment",
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!) |
Beta Was this translation helpful? Give feedback.
-
The only value of let is to prevent developer from changing a variable. Ha ha, why is variable then :). What is next, init time property setters like in C# or billion of setters and getters like in Java. You have alias-es, immutable references, immutable attributes. There are a lot of features to be done (bugs cleaning too). Make first them, then working superset of Python and than add what you want, but Mojo should never loose one feature - Speed. |
Beta Was this translation helpful? Give feedback.
-
Hi all, it has come time to close this proposal. For the record, Mojo still has support for strong immutability (e.g. borrowed arguments, immutable Reference), it only removed local immutability. We've discussed this, agreed on it, and executed towards it. Mojo 24.1 removed support for let from the compiler IR (but it still parses let for migration), subsequent releases will remove the let keyword entirely. |
Beta Was this translation helpful? Give feedback.
-
I consider removing "let" from Mojo a good pragmatic decision at this stage. Better get the basics right first, and extend the language in a principled way later. For instance, we can start from observing that we may have two boolean attributes for a "name bound to a value" concept: mutable type vs immutable type (let's call them here Wildcard vs Typed), and mutable value vs immutable value (Variable vs Constant). This generates four kinds of this concept: Wildcard Variable, Typed Variable, Wildcard Constant, Typed Constant. Another concept we could use is that of default context. For instance, default context for a "name bound to a value" in Python is Wildcard Variable. Default context for Swift is Typed, further concretized by "var" (Typed Variable) and "let" (Typed Constant). Wildcard Constant, while appearing perplexingly exotic, could be semantically beneficial for principled casting, as a Wildcard Constant with value "1", for instance, may be used for assignment to a Typed Variable with type Int, Float, Complex, String etc. For generality, it would be beneficial to allow specifying one of the four kinds of "name bound to a value" per each occurrence, whereas "def" and "fn" would, among other things, indicate the default contexts. The "def", Pythonic context, would stay as it is now for the foreseeable future. The "fn" may be extended with the explicit declarations of the four kinds. If we derive "wild" from "wildcard", the syntax could be even made amusing/invigorating. I'd love to be able to write "wild var" and perhaps "wild const" :) I think it would be easy to explain to students that all the names bound to values in classic Python are simply Wild Variables. |
Beta Was this translation helpful? Give feedback.
-
Following on quite a bit of discussion about keyword naming, confusion about how immutability works with reference semantics, confusion between runtime constants and compile time constants, I think we should simplify Mojo by removing named immutable values (aka
let
declarations and the keyword) entirely. I wrote up a proposal here, I'd love to know what folks think about it:https://github.com/modularml/mojo/blob/main/proposals/remove-let-decls.md
Note that this doesn't remove the notion of immutability entirely from Mojo, just let declarations, and a future lifetimes feature will include immutable references (which is critical for mutability^sharing checking), here's we're just talking about removing the
let
declaration. We've discussed it a bit internally to Modular and folks think this is a good idea, but I'd love to get questions and comments from y'all before doing anything here, thanks!-Chris
Beta Was this translation helpful? Give feedback.
All reactions