It is 35,000 times faster than Python. It is quicker than C. It is as easy as Python.
Enter Mojo: a newly released programming language made for AI developers and made by Modular, a company founded by Chris Lattner, the original creator of Swift.
It’s a superset of Python, combining Python’s usability, simplicity, and versatility with C’s incredible performance.
If you’re passionate about AI and already have a grasp on Python, then Mojo is definitely worth a try. So, let’s dive in and explore 7 powerful features of this exciting language together.
Mojo’s features
I signed up for Mojo access shortly after it was announced and got access a few days later.
I started exploring all the cool new features they had to offer and even had the chance to run some code and see the language in action. Here are 7 interesting Python upgrades I found:
1. let
and var
declarations
Mojo introduces new let
and var
statements that let us create variables.
If we like we can specify a type like Int
or String
for the variable, as we do in TypeScript. var
allows variables to change; let
doesn’t. So it’s not like JavaScript’s let
and var
– There’s no hoisting for var
and let
is constant.
def your_function(a, b):
let c = a
# Uncomment to see an error:
# c = b # error: c is immutable
if c != b:
let d = b
print(d)
your_function(2, 3)
2. struct
s for faster abstraction
We have them in C++, Go, and more.
Structs are a Mojo feature similar to Python classes, but they’re different because Mojo classes are static: you can’t add more methods are runtime. This is a trade-off, as it’s less flexible, but faster.
struct MyPair:
var first: Int
var second: Int
# We use 'fn' instead of 'def' here - we'll explain that soon
fn __init__(inout self, first: Int, second: Int):
self.first = first
self.second = second
fn __lt__(self, rhs: MyPair) -> Bool:
return self.first < rhs.first or
(self.first == rhs.first and
self.second < rhs.second)
Here’s one way struct
is stricter than class
: all fields must be explicitly defined:
3. Strong type checking
These structs don’t just give us flexibility, they let us check variable types at compile-time in Mojo, like the TypeScript compiler does.
def pairTest() -> Bool:
let p = MyPair(1, 2)
# Uncomment to see an error:
# return p < 4 # gives a compile time error
return True
The 4
is an Int
, the p
is a MyPair
; Mojo simply can’t allow this comparison.
4. Method overloading
C++, Java, Swift, etc. have these.
Function overloading is when there are multiple functions with the same name that accept parameters with different data types.
Look at this:
struct Complex:
var re: F32
var im: F32
fn __init__(inout self, x: F32):
"""Makes a complex number from a real number."""
self.re = x
self.im = 0.0
fn __init__(inout self, r: F32, i: F32):
"""Makes a complex number from its real and imaginary parts."""
self.re = r
self.im = i
Typeless languages like JavaScript and Python simply can’t have function overloads, for obvious reasons.
Although overloading is allowed in module/file functions and class methods based on parameter/type, it won’t work based on return type alone, and your function arguments need to have types. If don’t do this, overloading won’t work; all that’ll happen is the most recently defined function will overwrite all those previously defined functions with the same name.
5. Easy integration with Python modules
Having seamless Python support is Mojo’s biggest selling point by far.
And using Python modules in Mojo is straightforward. As a superset, all you need to do is call the Python.import_module()
method, with the module name.
Here I’m importing numpy
, one of the most popular Python libraries in the world.
from PythonInterface import Python
# Think of this as `import numpy as np` in Python
let np = Python.import_module("numpy")
# Now it's like you're using numpy in Python
array = np.array([1, 2, 3])
print(array)
You can do the same for any Python module; the one limitation is that you have to import the whole module to access individual members.
All the Python modules will run 35,000 times faster in Mojo.
6. fn
definitions
fn
is basically def
with stricter rules.
def
is flexible, mutable, Python-friendly; fn
is constant, stable, and Python-enriching. It’s like JavaScript’s strict mode, but just for def
.
struct MyPair:
fn __init__(inout self, first: Int, second: Int):
self.first = first
self.second = second
fn
‘s rules:
- Immutable arguments: Arguments are immutable by default – including
self
– so you can’t mistakenly mutate them. - Required argument types: You have to specify types for its arguments.
- Required variable declarations: You must declare local variables in the
fn
before using them (withlet
andvar
of course). - Explicit exception declaration: If the
fn
throws exceptions, you must explicitly indicate so – like we do in Java with thethrows
keyword.
7. Mutable and immutable function arguments
Pass-by-value vs pass-by-reference.
You may have across this concept in languages like C++.
Python’s def
function uses pass-by-reference, just like in JavaScript; you can mutate objects passed as arguments inside the def
. But Mojo’s def
uses pass-by-value, so what you get inside a def
is a copy of the passed object. So you can mutate that copy all you want; the changes won’t affect the main object.
Pass-by-reference improves memory efficiency as we don’t have to make a copy of the object for the function.
But what about the new fn
function? Like Python’s def
, it uses pass-by-reference by default, but a key difference is that those references are immutable. So we can read the original object in the function, but we can’t mutate it.
Immutable arguments
borrowed
a fresh, new, redundant keyword in Mojo.
Because what borrowed
does is to make arguments in a Mojo fn
function immutable – which they are by default. This is invaluable when dealing with objects that take up a substantial amount of memory, or we’re not allowed to make a copy of the object we’re passing.
For example:
fn use_something_big(borrowed a: SomethingBig, b: SomethingBig):
"""'a' and 'b' are both immutable, because 'borrowed' is the default."""
a.print_id() // 10
b.print_id() // 20
let a = SomethingBig(10)
let b = SomethingBig(20)
use_something_big(a, b)
Instead of making a copy of the huge SomethingBig
object in the fn
function, we simply pass a reference as an immutable argument.
Mutable arguments
If we want mutable arguments instead, we’ll use the new inout
keyword instead:
struct Car:
var id_number: Int
var color: String
fn __init__(inout self, id: Int):
self.id_number = id
self.color = 'none'
# self is passed by-reference for mutation as described above.
fn set_color(inout self, color: String):
self.color = color
# Arguments like self are passed as borrowed by default.
fn print_id(self): # Same as: fn print_id(borrowed self):
print('Id: {0}, color: {1}')
car = Car(11)
car.set_color('red') # No error
self
is immutable in fn
functions, so we here we needed inout
to modify the color
field in set_color
.
Key takeaways
- Mojo: is a new AI programming language that has the speed of C, and the simplicity of Python.
- let and var declarations: Mojo introduces
let
andvar
statements for creating optionally typed variables.var
variables are mutable,let
variables are not. - Structs: Mojo features static structs, similar to Python classes but faster due to their immutability.
- Strong type checking: Mojo supports compile-time type checking, akin to TypeScript.
- Method overloading: Mojo allows function overloading, where functions with the same name can accept different data types.
- Python module integration: Mojo offers seamless Python support, running Python modules significantly faster.
- fn definitions: The fn keyword in Mojo is a stricter version of Python’s
def
, requiring immutable arguments and explicit exception declaration. - Mutable and immutable arguments: Mojo introduces mutable (
inout
) and immutable (borrowed
) function arguments.
Final thoughts
As we witness the unveiling of Mojo, it’s intriguing to think how this new AI-focused language might revolutionize the programming realm. Bridging the performance gap with the ease-of-use Python offers, and introducing powerful features like strong type checking, might herald a new era in AI development. Let’s embrace this shift with curiosity and eagerness to exploit the full potential of Mojo.