Replies: 10 comments 18 replies
-
Just putting out the option that classes are in Mojo equivalent to Python and data grouping with functions is primarily done with traits/protocols and structs, similar to Rust. |
Beta Was this translation helpful? Give feedback.
-
The Cinder project (which I used to work on) has some prior art for making method lookups faster and having compact instance storage in Static Python. |
Beta Was this translation helpful? Give feedback.
-
That's quite a difficult problem. I can only share an idea that is going through my mind. I hope this might be helpful in some way. Classes are just syntactic sugar for __dict__ and __class__. This would require knowing which keys are accessed and whether this set of keys is finite. I also wanted to share with you two libraries I wrote to test some of my code that might also be useful to you.
They are useful, but may still have some problems. You may find them useful to test mojo. It's really cool what you are doing and I hope that you will be successful. |
Beta Was this translation helpful? Give feedback.
-
I believe this deserves a wide discussion about the priorities and main goals of mojo. define = True
class C:
print("hello") # prints 'hello'
if define:
def f(self): print(10)
else:
def f(self): print(20)
C().f() # prints '10'
@strict
class D:
print("hello") # prints 'hello'
if define:
def f(self): print(10)
else:
def f(self): print(20)
D().f() # should raise in class definition This would keep old Python-like classes working and easier to write. This is my point of view, but I think that this should be aligned with the goals of the language (maybe we want to be strict by default). I think the main point here for all the decisions commented is to align the design with the purpose of the language. Does mojo want to be Python-like in all this cases and specially mark what's designed for C-like performance? Do we aim to have C-like strict performance by default and make it clear when it's Python syntax and just reuse what Python does? |
Beta Was this translation helpful? Give feedback.
-
Then neither will it be a superset, unless you consider Julia a superset because it can call into cpython. |
Beta Was this translation helpful? Give feedback.
-
It would seem natural to default to |
Beta Was this translation helpful? Give feedback.
-
I vote for @Mogball's way of decorators. I'm so frustrated about the dynamism of python class. I had to add those needless The syntax is good to serve the performance first, then the compatibility second. |
Beta Was this translation helpful? Give feedback.
-
After these changes will dynamic/strict classes coexist with |
Beta Was this translation helpful? Give feedback.
-
If
In such case Python whole codebase could be just copied to Mojo project and it will work with some performance boost. If someone would like to squeeze performance, these .py files could be just transfered to .mojo files. This approach could be also much simpler to implement and maintain than "duality" in |
Beta Was this translation helpful? Give feedback.
-
Super interesting idea. We can definitely explore having the compiler treat .py files differently than .mojo files. |
Beta Was this translation helpful? Give feedback.
-
Mojo has the lofty goal of being a simple, powerful, and easy-to-use language like Python but with features that allow programmers to reach the performance of C. One of Mojo's approaches is to start from being a superset of Python and provide an incremental typing-for-performance story where the performance of Python code can be incrementally improved by adding type annotations, explicit variable declarations and error handling, switching
def
tofn
, and so on. By making things like types explicit, dynamism is removed from the program and the compiler can generate faster code. The relationship between Mojo and dynamism has to be carefully managed to meet the goals of the language. The point of this post is to measure where that relationship stands now, what it will need going forward as Mojo grows more features, and develop a framework for managing that relationship.Classes and Dynamism
One feature that Mojo lacks at the moment are classes, an inherently dynamic feature that provides inheritance and runtime polymorphism, the foundations of object-oriented programming.
Classes are implemented differently in many languages. In Python, for example, classes are much more flexible than in languages like C++ or Java -- they are more similar to classes in Javascript or Smalltalk. Methods can be defined and then deleted, and even conditionally defined! For example, this is valid Python code:
In fact, the body of a Python class is just code that is executed, and the resulting local variables are bound to the attributes of a class object. When calling a class object, it returns a new object with a reference to the class object, in which it can perform attribute lookup. In addition, functions that would be member functions have their first argument bound to the new class instance through the Python descriptor mechanism.
Mojo as a superset of Python has to support the full "hash-table" dynamism in classes for compatibility with Python, but reference semantic classes are also important for systems programming and application programming, where this level of dynamism isn't needed and is actively harmful. We need to decide how to handle this.
One approach is to provide a decorator on class definitions (which can be opt-in or opt-out) to indicate whether the class is "fully dynamic" as in Python or whether it is "constrained dynamic" (e.g. has virtual methods that may be overridden but cannot have methods added or removed).
"Constrained dynamic" Mojo classes will use vtables for a more limited but more efficient constrained dynamism than full hash table lookups. In addition to raw lookups, constrained dynamic classes can use "class hierarchy analysis" to devirtualize and inline method calls, which are not valid for "fully dynamic" classes.
Swift has a similar issue, where the developers wanted to have constrained dynamism by default but needed full dynamism when working with Objective-C code: Objective-C is based on the Smalltalk object model and thus has the same issues as Python. Swift solved this by adding an opt-in @objc decorator, which provides full compatibility with Objective-C classes. Swift implicitly applies this decorator to subclasses of Objective-C or
@objc
classes for convenience.If we chose to follow this design in Mojo, we could introduce a
@dynamic
decorator, in which the class is an instance of a hash table and the body is executed at runtime:We could of course make dynamic be the default, and have a decorator like
@strict
to opt-in to constrained dynamism as well. Regardless of the bias, we absolutely need to support full dynamism to maintain compatibility with Python.An implementation question here would be "when does the body get executed?" when the class is defined at the top-level. In this case, the class
C
could be treated as a global variable with a static initializer that is executed when the program is loaded. This ties into a discussion about how to treat global variables and top-level code in general, which will come in a subsequent section. Naturally, if the class is never referenced, the body is never parsed and the static initializer is never emitted.Syntactic Compatibility and
@dynamic
A primary goal of Mojo is to minimize the syntactic differences with Python. We also have to balance that need with what the right default for Mojo is, and this affects the bias on whether this decorator is "opt-in" or "opt-out".
We find it appealing to follow the Swift approach by making "full dynamic" an opt-in choice for a Mojo class. This choice would add another syntactic divergence between Mojo and Python, but it is one that can be alleviated with an automatic mechanical tranformer from Python code to Mojo code (e.g. to deal with new keywords we take). In this case, all Python classes will be translated by sticking
@dynamic
on them, and they can be removed for incremental boosts to performance.An alternate design is to require opt-in to "constraint dynamism" by adding a
@strict
(or use another keyword altogether) for vtable dynamism. We can evaluate tradeoffs as more of the model is implemented.def
and DynamismIn Mojo, the goal of
def
is to provide a syntactic feature set that enables compatibility with Python. It allows, for example, omitting type annotations, implicit variable declarations, implicit raises, etc. But the Mojodef
is not the same as a Pythondef
. A commonly reported issue is that Mojo scoping rules differ from Python's. In Python, local variables are scoped at the function level, but Python also supports behaviour like:Python functions also have a notion of which names are supposed to be bound to local variables. In the following example,
bar
knowsi
refers to a captured local variable infoo
, whereasbaz
tries to retrieve a value fori
in its local variable map.This gets at the heart of how Mojo should treat implicitly declared variables in
def
s. The short answer is: exactly how Python does.def
s should carry a function-scoped hash table of local variables that is populated and queried at runtime. In other words, lookup of implicitly-declared variables woudl be deferred to runtime. On the other hand, the function does need to have a notion of what variable could be available in the function, in order to emitUnboundLocalError
s as required. Of course, the compiler can optimize the table away and do all the nice stuff compilers do if possible.Difficulty arises when discussing
def
s themselves. Althoughdef
s should internally support full hashtable dynamism, what kind of objects aredef
s themselves? For instance:In Mojo today, the first time the name lookup of
bar
is resolved, it is baked into a direct call to the firstbar
. Therefore, shadowing ofbar
does not propagate into the body offoo
. On the other hand, if alldef
s were treated as entries in a hashtable, then it would.One middle-ground approach would be to treat
bar
as a mutable global variable of typedef()
(one for each possible overload ofbar
). The dynamism can be escalated with a@dynamic
decorator that removes static function overloading. However, both of these approaches risk creating confusing name lookup rules. For instance, would the following be allowed?This gets into the "levels of dynamism" Mojo intends to provide, and how that relates to
def
s. The reality is thatdef
s in Mojo today only resemble Pythondef
s on the surface. They share similar syntax, but Mojodef
s are really extra syntax sugar on top offn
and are altogether a different beast than Pythondef
s.Four Levels of Dynamism
To summarize, in order to support incremental typing-for-performance, Mojo will have to support everything from strict, strongly-typed code to full Python hashtable dynamism but with syntax that provides a gradual transition from one end to the other.
Given all that has been discussed and what the language looks like today, Mojo's dynamism is moving into four boxes:
The fourth category isn't explored here, but will important when/if we support subclassing imported-from-CPython classes in Mojo, because that will fix the runtime in-memory representation to what CPython uses.
The highest level of dynamism and the most faithful compatibility doesn't come from Mojo itself, it comes from Mojo's first class interoperability with CPython. This in effect will be Mojo's escape hatch for compatibility purposes and is what gives Mojo access to all of Python's vast ecosystem. Below that, Mojo will provide an emulation of Python's hash-table dynamism that is a faithful but not quite identical replication of Python behaviour (no GIL, for example!). Building this out will be a huge undertaking, and is something Mojo should do over time.
The most important thing to remember is that Mojo is not a "Python compiler". The benefit of sharing the same syntax as Python, however, means seemless interop is on the table:
The goal of the "levels of dynamism" is to provide an offramp, starting by removing the
@python
decorator frompython_func
.Beta Was this translation helpful? Give feedback.
All reactions