mojo language – Sesame Disk https://sesamedisk.com Sat, 24 Jun 2023 16:52:33 +0000 en-US hourly 1 https://wordpress.org/?v=6.4.3 https://sesamedisk.com/wp-content/uploads/2020/05/cropped-favicon-transparent-32x32.png mojo language – Sesame Disk https://sesamedisk.com 32 32 Mojo Programming for AI: Better than Python? https://sesamedisk.com/mojo-programming-for-ai-better-than-python/ https://sesamedisk.com/mojo-programming-for-ai-better-than-python/#respond Sat, 24 Jun 2023 16:48:42 +0000 https://sesamedisk.com/?p=10077 If you’re passionate about staying at the cutting edge of AI innovation, you’re in the right place! As we lunge into the future, being at the forefront of AI technology is no longer just an advantage—it’s a necessity. This is where Mojo programming comes into the fray.

Now, most of us rightfully associate AI / ML programming with Python. So where does Mojo programming language come in? That’s what this follow-up article on Mojo aims to find out after we explored the fundamentals of the Mojo language in our previous article.

Mojo Programming for AI: Better than Python?

Why Choose Mojo Programming Language?

You wouldn’t be silly to ask this question when Mojo programming language hasn’t even publically released yet. In fact, it’s a smart question. But do you know what it means? Opportunity. And do you know who is behind this new language? Chris Lattner, the creator of Swift programming language. We know how that turned out. And he’s also the co-founder of LLVM, Clang compiler, and MLIR compiler. So, this is the best time to fine-tune your skills to specialize in a language most people will not know about until later.

And choosing Mojo programming language for AI development carries significant benefits. Modular’s powerful AI engine, Mojo’s ability to support existing Python code and infrastructure, and its high performance make it a compelling choice for developers. That’s not all! Mojo language also aims to be a complete superset of Python before release.

But is the hype real, especially when it comes to its performance?

Mojo Programming Language, Analyzed

While Python has been the go-to language for various tasks over the years, Mojo’s features are specifically tailored for AI and systems development. Remember that Mojo programming language aims to deal with the pain points.

For instance, it offers in-built support for concurrent and distributed computing and parametric metaprogramming—fundamental attributes for today’s data-intensive AI applications. This makes Mojo a strong contender compared to Python in AI development.

And yet… how exactly does it compare to Python? Is Mojo is better than Python? Let’s take a deeper look.

Python vs Mojo: A Comprehensive Comparison

What’s really exciting is that the Mojo playground allows you to use the default Python interpreter for the Python code versions of any program. It will make more sense once I show you how.

All you have to do is add this to the top of your Jupyter notebook cell in the Mojo programming playground to indicate that the cell contains Python code.:

%%python

Then, you can write any Python code, and it will run as if on native Python.

For instance, this compiles in the Mojo playground even though it doesn’t natively support lists or list functions. It works because it executes using the Python interpreter.

%%python
data = [1, 2, 3, 4, 5]

# Calculate the mean (average) of the numbers
mean = sum(data) / len(data)

# Print the mean
print(mean)

#output: 3.0

Note that the variables, functions, and imports defined in a Python cell are available for access in future Mojo cells. This lets you write and execute normal Python code within the Mojo programming playground. And you can neatly integrate it with native Mojo language code too. How futuristic is that?

That said, it’s time to explore the key differences between Python and Mojo programming language.

Basic Data Types: How are Integer and String Different in Mojo Programming Language

If you have been paying attention, you must have noticed that Mojo uses Int (with strong type checking), with a capital I, compared to int in Python, with a lowercase i. This is on purpose.

Mojo’s Int type aims to be simple, fast, and perform better. On the other hand, int in Python is dynamically allocated space and doesn’t have a fixed limit on its size. Mojo’s Int is a fixed 32-bit signed integer, while Python’s int is an arbitrary precision integer. Of course, a fixed-size type also presents problems like overflow and underflow, which are not present in Python.

Mojo fixes the slower performance of Python’s data types by utilizing stronger type checking, like in C++ and Swift (Lattner, remember?). Thus, Mojo’s Int will likely have more consistent and faster performance compared to Python’s int. Especially when it comes to larger data sets or more complex calculations.

Here’s an example highlighting the difference in the variable declaration for integer type.

#mojo encourages strong type checking but allows implicit variable data types too
var number: Int = 10
#this also works:
var number = 10

#python does not support explicit or strong type checking
number = 10

In Python, the variable dynamically takes on the type for the value of 10. And the string data type is also different. In Mojo programming, the String type is imported from the String module. Instead of using the str() method for type conversion as in Python, Mojo uses the String() constructor. Another key difference is that Mojo only supports commas to separate or concatenate strings and variables.

To work with strings, you must add this line to your code:

from String import String

#Strings in Mojo langauge, including the import, String constructor, and ',' for concat

from String import String
let name: String = "Umar"
let age: Int = 99
print("Hello, my name is ", name, "and I'm ", String(age), "years old.")

# Strings in Python using built-in str data type, str() method for type conversion, and supports '+' or ',' for concat

name = "UmarB"
age = 92
print("Hello, my name is " + name + " and I'm " + str(age) + " years old.")  # Output: Hello, my name is Alice and I'm 25 years old.

The outputs of both code snippets are similar, as shown below, except for better spacing in the Python version.

data types, integer, string in mojo programmng language

Beyond data types, the differences also extend to function declaration.

Comparing ‘fn’ and ‘def’ Functions in Mojo Programming: Unveiling Control, Mutability, and Scoping Differences

Both fn and def keywords can be used to define functions in Mojo, are interchangeable at the interface level, and have parameters and return values. They can also raise exceptions using the raises function effect.

All values passed in Mojo functions use value semantics by default, unlike Python. Mojo functions, by default, receive a copy of arguments and can change them inside the function scope with no visible change outside.

Here is a list of comparison between fn and def:

  • fn provides a more controlled and restricted environment inside its body compared to def.
  • In a fn declaration, argument values default to being immutable and require explicit type specifications, similar to let declarations, while in def they default to being mutable and argument declaration doesn’t have to be explicit.
  • The inout modifier in fn indicates that the parameter can be mutated, and the changes will be reflected outside the function, like in Python. def functions do not support the inout parameter modifier, while fn functions allow specifying inout parameters.
  • fn functions require explicit type annotations for parameters and the return type. If the return type is not explicitly declared, it is interpreted as returning None. In contrast, def functions can have implicit return types, where if a return statement is missing, it will implicitly return None.
  • fn functions support the owned parameter modifier, which indicates ownership transfer, allowing explicit control over memory management, while def functions do not have an equivalent modifier.
from String import String

def greet(name):
    print("Hello,")
    print(name)

fn farewell(name: String):
    print("Goodbye, ", name)

# Both can be called with the same syntax
greet("Alice")
farewell("Bob")

def add(x, y):
    return x + y

fn subtract(x: Int, y: Int) -> Int:
    return x - y

print(add(1,2))
print("Subtract: ", subtract(5,2))

Here is the output of the code above:

Comparing 'fn' and 'def' Functions: Unveiling Control, Mutability, and Scoping Differences

To recap, `def` is defined by necessity to be very dynamic, flexible and generally compatible with Python: arguments are mutable, local variables are implicitly declared on first use, and scoping isn’t enforced. This is great for high level programming and scripting, but is not always great for systems programming. To complement this, Mojo provides an `fn` declaration which is like a “strict mode” for `def`.

Note:

It’s not possible to use print(“Hello, “, name) inside the def function. This is the error you get: no matching function in call to ‘print’:

Struct vs. Class: Contrasting Data Containers in Mojo and Object-Oriented Classes in Python

The Mojo programming language does not support classes yet. Currently, it only supports structures. Structs in Mojo programming, similar to classes in Python language, can have member variables and methods. They are also static and inline their data within their container. On the contrary, classes in Python have a more dynamic nature and provide extensive support for OOP concepts.

Here is a comprehensive comparison between structs and classes:

  • Structs provide a static and compile-time bound approach, unlike the dynamic nature of classes in Python.
  • Structs in Mojo are inlined into their container instead of being implicitly indirect and reference counted. Structs primarily focus on data representation rather than behavior. Classes, on the other hand, allow for inheritance and polymorphism, facilitating code reuse and extensibility.
  • Structs offer indirection-free field access, allowing direct access to fields without method calls. But classes can have methods that encapsulate behavior and can be called on instances of the class.
  • Structs are often used for performance-critical scenarios and low-level systems programming. However, classes commonly building complex software systems, model real-world entities, and organize code in a modular way.
#creating a Person struct with functions to greet

from String import String
@value
struct Person:
    var name: String
    var age: Int

    fn greet(self):
        print("Hello, my name is ", self.name, "and I'm ", String(self.age), "years old.")

# Creating an instance of the Person struct
person = Person("Umar", 22)
person.greet()


#creating a class and methods in Python for greeting a person

%%python
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greet(self):
        print("Hello, my name is " + self.name + " and I'm " + str(self.age) + " years old.")

# Creating an instance of the Person class
person = Person("Bukari", 30)
person.greet()

Note that as I pointed out in the last article, a complete guide to Mojo language, using the @value decorator allows you to skip boilerplate code such as the init method. As shown in the code above, the class uses an init method. However, in the struct in Mojo uses the decorator to create the boilerplate methods implicitly.

This is the code output in Mojo programming:

Comparing structs and classes in mojo language

Importing Python Libraries in Mojo Programming: Leveraging Python’s Existing Rich Ecosystem

The cool thing about Mojo programming language is that you can use Python’s existing rich and versatile ecosystem of libraries! Don’t believe me? Give me a moment, and I will show you.

At the top of your Mojo programming code, add this line:

from PythonInterface import Python

After that, you can use the import_module function to add any Python library to your Mojo program.

from PythonInterface import Python
# This is equivalent to Python's `import numpy as np`
let np = Python.import_module("numpy")


# Now use numpy as if writing in Python
array = np.array([1, 2, 3])
print(array)
import python libraries and using numpy to create a vector ndarray

Just like that, you can use a variety of other Python libraries already!

And now, let’s discuss the fabled high performance of Mojo. Is it really so fast?

Performance Comparison: Mojo vs Python language

The CEO of Modular, Chris Lattner claims Mojo is 35 THOUSAND times faster than Python. Yes, you read that right. Can you even imagine that? Note that this metric is is for Mojo-optimized code only.

So, for now, we will test a matrix multiplication Python function and then convert the code to Mojo language. After that, we will compare the benchmark for performance simply by using the same Python implementation in Mojo code.

Here is the Python implementation of matrix multiplication on the Mojo playground:

%%python
import numpy as np
from timeit import timeit

class Matrix:
    def __init__(self, value, rows, cols):
        self.value = value
        self.rows = rows
        self.cols = cols
        
    def __getitem__(self, idxs):
        return self.value[idxs[0]][idxs[1]]
    
    def __setitem__(self, idxs, value):
        self.value[idxs[0]][idxs[1]] = value

def benchmark_matmul_python(M, N, K):
    A = Matrix(list(np.random.rand(M, K)), M, K)
    B = Matrix(list(np.random.rand(K, N)), K, N)
    C = Matrix(list(np.zeros((M, N))), M, N)
    secs = timeit(lambda: matmul_python(C, A, B), number=2)/2
    gflops = ((2*M*N*K)/secs) / 1e9
    print(gflops, "GFLOP/s")
    return gflops

def matmul_python(C, A, B):
    for m in range(C.rows):
        for k in range(A.cols):
            for n in range(C.cols):
                C[m, n] += A[m, k] * B[k, n]

After that, run this command in the next cell:

python_gflops = benchmark_matmul_python(128, 128, 128).to_float64()

This is the output on my machine (MacBook M1):

Python code run in Mojo playground shows that it is run at 0.002 GFLOP/s

Next, let’s modify this Python code for a Mojo program implementation:

#|code-fold: true
#|code-summary: "Import utilities and define `Matrix` (click to show/hide)"

from Benchmark import Benchmark
from DType import DType
from Intrinsics import strided_load
from List import VariadicList
from Math import div_ceil, min
from Memory import memset_zero
from Object import object, Attr
from Pointer import DTypePointer
from Random import rand, random_float64
from TargetInfo import dtype_sizeof, dtype_simd_width

# This exactly the same Python implementation, 
# but is infact Mojo code!
def matmul_untyped(C, A, B):
    for m in range(C.rows):
        for k in range(A.cols):
            for n in range(C.cols):
                C[m, n] += A[m, k] * B[k, n]
fn matrix_getitem(self: object, i: object) raises -> object:
    return self.value[i]


fn matrix_setitem(self: object, i: object, value: object) raises -> object:
    self.value[i] = value
    return None


fn matrix_append(self: object, value: object) raises -> object:
    self.value.append(value)
    return None


fn matrix_init(rows: Int, cols: Int) raises -> object:
    let value = object([])
    return object(
        Attr("value", value), Attr("__getitem__", matrix_getitem), Attr("__setitem__", matrix_setitem), 
        Attr("rows", rows), Attr("cols", cols), Attr("append", matrix_append),
    )

def benchmark_matmul_untyped(M: Int, N: Int, K: Int, python_gflops: Float64):
    C = matrix_init(M, N)
    A = matrix_init(M, K)
    B = matrix_init(K, N)
    for i in range(M):
        c_row = object([])
        b_row = object([])
        a_row = object([])
        for j in range(N):
            c_row.append(0.0)
            b_row.append(random_float64(-5, 5))
            a_row.append(random_float64(-5, 5))
        C.append(c_row)
        B.append(b_row)
        A.append(a_row)

    @parameter
    fn test_fn():
        try:
            _ = matmul_untyped(C, A, B)
        except:
            pass

    let secs = Float64(Benchmark().run[test_fn]()) / 1_000_000_000
    _ = (A, B, C)
    let gflops = ((2*M*N*K)/secs) / 1e9
    let speedup : Float64 = gflops / python_gflops
    print(gflops, "GFLOP/s, a", speedup.value, "x speedup over Python")
    

In the next cell, run this line:

benchmark_matmul_untyped(128, 128, 128, 0.0022926400430998525)

On my machine, this code runs nearly 5x times faster, with minimal optimization. We simply converted the Python code to Mojo’s native code:

Mojo code is 5x times faster than Python language as shown in the image and runs at less than 0.01 GFLOP/s for matrix multiplication

I also want to show you this Mojo code from the playground:

fully optimized Mojo code runs 14000X faster than Python code for matrix multiplication

The optimized Mojo code for matrix multiplication runs 14000x times faster. Even if it’s not the same on your machine, it is still unprecedented!

Aren’t you interested to learn more about Mojo programming’s role in AI development now?

Mojo’s Role in AI Development: The Need for Powerful Tools in a Competitive Future

Modular‘s AI engine natively supports dynamic shapes for AI workloads, based on a new technology called Shapeless, outpacing other statically shaped compilers. Shapeless allows Modular to represent and manipulate variable-length inputs without having to know their exact shapes in advance. The AI engine is also fully compatible with existing frameworks and servers, and it allows developers to write their own custom operators. As a result, Modular’s engine can deploy AI models in a variety of environments, including the cloud, on-premises, and edge devices.

Impressively, the Modular AI Engine exhibits speedups of 3x-9x versus the default TensorFlow on BERT, a highly optimized language model. While XLA necessitates a significantly longer compile time than TensorFlow, it does offer superior execution performance. Despite this, the Modular AI Engine continuously outperforms, delivering a 2x-4x faster and superior performance than XLA.

Comparing Mojo’s AI Engine to Python Engines:

  • Mojo’s AI engine is a good choice if you need to build a model with dynamic shapes. Python engines can also build models with dynamic shapes, but they may require more code and be less efficient.
  • If you need to deploy your model on various platforms, Mojo’s AI engine is a good choice. Python engines can also be deployed on a variety of platforms, but Mojo’s AI engine is designed to be more portable.
  • If you need to write custom operators for your model, Mojo’s AI engine is a good choice. Python engines can also write custom operators, but Mojo’s AI engine makes it easier.

Conclusion: The Future Scope of Mojo Programming for AI

When you choose to embark on the journey of Mojo programming for AI development, you’re not only adopting a programming language—you’re aligning with a philosophy that prioritizes performance, flexibility, and user-friendliness, as explored in our recent article: Mojo Language 101. As Mojo progresses to be a complete superset of Python before its public release, early adoption and specialization in Mojo programming could offer significant advantages in the future.

The emerging Mojo programming language’s tailored features for AI and systems development, along with compatibility with Python code and libraries, make it a potential game-changer. With distinct advantages over Python, including high performance, in-built support for concurrent and distributed computing, and superior handling of data types, Mojo positions itself as a powerful tool for AI development.

From matrix multiplication benchmarks that indicate Mojo to be up to five times faster than Python, to the staggering claim of fully optimized Mojo code running 14,000 times faster, the future of AI and systems development could be revolutionized. But Mojo’s proficiency is not limited to its speed; it also boasts superior compatibility and a more streamlined approach to handling dynamic shapes in AI workloads!

Please comment your thoughts on Mojo programming language and share with your friends if you found it helpful.

Related articles: Mojo Language Complete Guide and Classification of NSFW Images.

Written by: Syed Umar Bukhari.

]]>
https://sesamedisk.com/mojo-programming-for-ai-better-than-python/feed/ 0
Mojo Language 101 🔥: Complete Guide https://sesamedisk.com/mojo-language-101-complete-guide/ https://sesamedisk.com/mojo-language-101-complete-guide/#respond Tue, 13 Jun 2023 19:28:52 +0000 https://sesamedisk.com/?p=9892 Ever feel like you’re swimming against the tide, striving to make sense of the growing complexities and progress of artificial intelligence development? In our turbocharged, competitive world, the mantra is clear: adapt, improve, and excel. Based on this, Mojo language, the innovative programming language designed explicitly for AI development, entered the frame (just over a month ago)!

mojo language 101 complete guide written by syed umar bukhari

In this article, we’ll dissect the Mojo language and its technical wizardry with carefully crafted examples that will highlight its key features. For your help, you can visit this GitHub link to access all the code used for this article. Get ready to unveil why Mojo could be the ace up your sleeve for your next big venture in the field of AI.

Presenting the Mojo Language: Overview and Context

Are you prepared to elevate your AI development capabilities to new heights? Before you can do that, knowing what you’re getting yourself into is essential, right?

Let’s begin exploring the new Mojo programming language!

Understanding Mojo language is more than learning its syntax—it’s about exploring the philosophy behind its creation. The team at Modular crafted this language with an emphasis on performance, modularity (pun intended), and interoperability.

Do you want to start using Mojo language to follow along? Remember, you have to sign up here to get started. It took me under two weeks to access the Mojo playground (where you can currently code in Mojo). So, sign up soon if the Mojo language fascinates you. I know people who got in just a day after queuing up!

Whether you’re a beginner or an experienced programmer, Mojo has the potential to revolutionize your approach to AI development, thanks to its innovative features and intuitive syntax. This language aims to run efficiently and promote clean and organized codebases. But what makes it unique?

Understanding the Mojo Language: What Sets It Apart?

Mojo language, created for AI tasks and system programming by unifying the AI/ML infrastructure, is a testament to Modular’s acute attention to detail. It simplifies the intricacies often linked with AI programming. As such, Mojo language aims to be a complete superset of Python, but it’s not there yet.

The goal of the Mojo language is innovation. To target accelerators and provide a language with robust compile time metaprogramming, caching and other prevalent modern features.

Mojo boasts dynamic and static typing, affording programmers the flexibility to switch their coding styles as per their needs. The syntax of Mojo language is intuitive and designed with programmers in mind, further simplifying the process of getting started.

This paves the way for swift and efficient coding, reducing the time taken to transform ideas into functional AI models. What’s more is that it also works seamlessly with different systems and languages, increasing its versatility in diverse development environments.

And you must sign up to get in the queue to access the Mojo Playground by Modular. The Mojo language is not publicly released yet.

In the next section, we will examine some of its main features, assuming you have access to Mojo language.

Why Mojo Programming: A Look at its Key Features

Mojo aims to bridge the gap between Python’s functionality and C++’s execution performance. It also aims to be a superset of Python, meaning it will eventually support all Python code.

Let’s see some of the key Mojo language features:

  • Expressiveness: Mojo language supports dynamic and static typing and features a rich standard library.
  • Concurrency: It has built-in features to support concurrent and distributed computing.
  • Modularity: Mojo’s architecture encourages modular design, allowing you to maintain codebases efficiently.
  • Interoperability: Mojo is highly compatible, allowing it to interact with different systems and programming languages, especially Python.
  • Performance: The language aims to deliver high performance, enabling rapid development and ease of use.
  • Mojo’s 🔥 Extension: While the file extension makes no difference to Mojo’s features, how cool is an emoji for a coding file extension?

With the features out of the way, it’s time to dive into the meat of things truly: the anatomy of the Mojo language.

The Anatomy of the Mojo Language: A Practical Guide to Writing Code

It’s vital to know the key elements that form the core structure of the Mojo programming language. Since it supports a lot of existing Python syntax, you can still do something like this and have it work:

print("Hello Mojo")

But that said, let’s explore Mojo language’s anatomical features:

let and var declaration in Mojo:

let and var are part of the Mojo language’s motto of adding strong type-checking to variable data types. You could say the JavaScript inspiration is obvious here. Still, both types:

  • Create a new scoped runtime value.
  • Support name shadowing, allowing variables in inner scopes to have the same name as variables in outer scopes.
  • Can include type specifiers, patterns, and late initialization.

Difference between let and var :

Aside from these similarities, there is a crucial difference between let and var in Mojo:

  • Variables declared with var are mutable, allowing their values to be modified after assignment.
  • Variables declared with let are immutable, meaning their values cannot be changed once assigned.

Here’s an example to highlight the features of both:

def bookstore_management(new_books, sold_books):
    # Declaring an immutable variable 'total_books' with 'let'
    let total_books = new_books
    print("Total books:")
    print(total_books)
    # Uncommenting the next line would result in an error because 'total_books' is immutable
    # total_books = total_books - 40  # error: 'total_books' is immutable
    # Declaring a mutable variable 'current_books' with 'var'
    var current_books = new_books
    print("Current books:")
    print(current_books)
    # Selling some books and reducing the current_books number
    current_books = current_books - sold_books
    print("Current books after reduction:")
    print(current_books)

    # Both 'let' and 'var' support name shadowing and lexical scoping
    if total_books != current_books:
        let total_books = current_books
        print("Total books with lexical scoping, declared inside nested scope:")
        print(total_books)
    print("Total books in bookstore management function is still:")
    print(total_books)
def run_bookstore():
    # Received 100 new books
    new_books = 100

    # Sold 20 books
    sold_books = 20

    bookstore_management(new_books, sold_books)

run_bookstore()


In the bookstore_management function, we create the total number of books initially as an immutable value using let. Afterward, we create a mutable variable current_books using var, which can be changed when books are sold.

This is how the output looks:

let and var declaration in Mojo

If you try to uncomment the line above, this is the error you will see:

let is immutable and gives an error if you try to modify it

The concept of name shadowing and lexical scoping allows you to create new variables (in the if scope) with the same names as outer variables within nested scopes. This variable shadows the total_books declared at the start of the function, showcasing the shadowing concept in lexical scoping. It prevents unintended interference between variables of the same name in different scopes.

Here is another Mojo code example which mainly highlights these other features:

def bookstore_management(new_books: Int, sold_books: Int):
    # Declaring an immutable variable 'total_books' with 'let'
    let total_books: Int = new_books
    print("Total books in store:", total_books)

    # Declaring a mutable variable 'current_books' with 'var'
    var current_books: Int = new_books
    print("Current books in store:", current_books)

    # Selling some books and reducing the current_books number
    current_books = current_books - sold_books
    print("Current books after selling:", current_books)

    # Both 'let' and 'var' support name shadowing and lexical scoping
    if total_books != current_books:
        let total_books: Int = current_books
        print("Total books with lexical scoping, declared inside nested scope:", total_books)

    # Late initialization and pattern matching
    let discount_rate: F64
    let book_id: Int = 123
    if book_id == 123:
        discount_rate = 0.2  # 20% discount for mystery books
    else:
        discount_rate = 0.05  # 5% discount for other book categories
    print("Discount rate for Book with ID ", book_id, "is:", discount_rate)

def run_bookstore():
    # Received 100 new books
    let new_books: Int = 100

    # Sold 20 books
    let sold_books: Int = 20

    bookstore_management(new_books, sold_books)

run_bookstore()

Here’s the updated output:

updated output Mojo language to show type specifiers, late initialization, and pattern matching

Type specifiers:

The type of each variable is explicitly specified using type specifiers (Int for integers and F64 for floating-point numbers). This ensures clarity and allows the compiler to enforce type safety.

Late initialization and pattern matching:

discount_rate demonstrates late initialization, where the value of a variable is determined based on a condition after its declaration with let. It is assigned a value later based on the book_category using conditional statements (if, elif, and else) –> pattern matching. Access out all the code here.

Notes:
  • I had trouble printing a string and variable inside one print statement in the first example, so I had to improvise and you two different print statements. Despite trying various string concatenation and type conversion approaches, I couldn’t get it to print the variable and string together. However, it was an isolated incident, and I couldn’t get it to replicate.
  • I am still determining all the data types specified by Mojo language. I tried STR for strings and I32 for integers, but both did not exist. But then with some research, I learned about a few other types in Mojo language other than Int and Float:
    • SI (signed integer — from the Mojo docs),
    • UI (unsigned integer), and
    • String (if you add this line at the top: from String import String).
  • UPDATE: Here is an indirect link which specifies the data type specifiers in Mojo.

Structures in Mojo Language:

Mojo language also supports structures, like in C++. A struct is a custom data structure that groups related variables of different data types into a single unit that holds multiple values. Functional programming languages commonly use them as lightweight alternatives to classes for creating custom data types, such as in C and C++, as they bind at compiler-time.

What are Structs in Mojo Language?

Structs in Mojo structs provide a way to organize and manage data, encapsulate attributes, and improve code organization and reusability.

  • Structs in Mojo have a static structure, and you cannot modify them at runtime like Python classes, offering performance benefits and safety.
  • Like Python classes, structs support methods, fields, operator overloading, and metaprogramming.
  • All instance properties in a struct must be explicitly declared with var or let. (right now only var is supported)
  • All standard data types, like Int, String, Bool, etc., make use of structs in Mojo.

Here’s an example of a struct that holds employee information:

from String import String
@value
struct Employee:
    var name: String
    var age: Int
    var department: String
    var salary: F64
    
def print_employee_details(employee: Employee):
    print("Name:", employee.name)
    print("Age:", employee.age)
    print("Department:", employee.department)
    print("Salary:", employee.salary)

# Create employee instances
employee1 = Employee("Alice Thompson", 30, "Engineering", 5000.0)
employee2 = Employee("Robert Davis", 35, "Sales", 4500.0)

# Print employee details
print_employee_details(employee1)
print_employee_details(employee2)

In this example, we define an Employee struct with properties such as name, age, department, and salary. Then, we create two instances of the Employee struct with different values for each property. The print_employee_details function prints the details of these two instances of the struct as you cannot directly print out a struct yet in Mojo.

The struct helps us organize and encapsulate employee data, efficiently creating, accessing, and managing employee information. We can also add additional methods to the struct to perform operations specific to employees, such as calculating bonuses or generating reports.

This example demonstrates how structs in Mojo provide a struct(ured) and efficient way to manage and manipulate real-world entities’ data. You should know that you cannot change the structure or contents of a struct while a program is running.

Here’s the output from the code above in Mojo language:

employee struct in mojo language

Note: While the Mojo docs specify that you can use let and var within a struct, let is not currently supported.

Overloading: Methods, Functions and Operators in Mojo

Mojo takes on a detective role whenever it needs to resolve calls. It scrutinizes each potential method: if only one fits the bill, that’s the chosen one. If there’s more than one contender, Mojo picks the one that’s the closest match. And in the rare scenario when Mojo can’t decide, it sounds an ambiguity alert. But don’t worry, you can overcome this by adding an explicit cast at the call site.

Method Overloading within Structs in Mojo:

In Mojo, methods are functions within a struct that can operate on the instances of that struct or class, accessing or modifying the data. And when you define a method in a struct or class without specifying argument data types in Mojo language, it handles them dynamically, like Python.

Yet, Mojo takes it a step further, offering full-fledged support for method overloading when type safety is a priority. The unique aspect of this feature? You can define multiple methods, each with the same name but different arguments, all within a single struct or class. This is a powerful tool, often seen in robust languages like C++, Java, and Swift, but now in a language focused on AI development too.

Let’s expand upon our Employee example from above:

from String import String
@value
struct Employee:
    var name: String
    var age: Int
    var department: String
    var salary: F64

    def print_details(self) -> None:
        print("Employee Name: ", self.name)
        print("Employee Age: ", self.age)
        print("Department: ", self.department)
        print("Salary: ", self.salary)
    def print_details(self, include_salary: Bool) -> None:
        print("Employee Name: ", self.name)
        print("Employee Age: ", self.age)
        print("Department: ", self.department)
        if include_salary:
            print("Salary: ", self.salary)

let employee1 = Employee("Alice Johnson", 30, "Engineering", 5000.0)
let employee2 = Employee("Robert Davis", 35, "Sales", 4500.0)

employee1.print_details()  

employee2.print_details(False)

Take the Employee struct, for instance, which sports two methods named print_details. The first one only calls for self, while the second one invites an additional Bool argument, include_salary. Now, when you want to execute print_details() on an Employee instance, Mojo programming language, with its sharp judgment, selects the correct method based on the arguments you provide. Hence, this example demonstrates the concept of method overloading in Mojo.

method overloading in mojo

However, I’d also like to point out that Mojo does not allow method overloading based solely on the return type. You can also check out all the code here. Let’s see how you can utilize function overloading.

Function Overloading in Mojo Language:

Mojo also natively supports function overloading and works similarly to other languages. This means that you can define multiple functions with the same name but with different arguments (type, number of arguments), and Mojo will differentiate each of them based on the function arguments.

Let’s consider the example below:

from String import String

# Function Overloading
fn print_details(name: String, age: Int) -> None:
    print("Name: ", name)
    print("Age: ", age)

fn print_details(name: String, age: Int, department: String) -> None:
    print("Name: ", name)
    print("Age: ", age)
    print("Department: ", department)

# Usage example
print_details("Alice Thompson", 30)
print_details("Robert Davis", 35, "Sales")

Two functions exist with the same name, but different arguments in the example above, one that takes two arguments and the other takes three arguments.

how function overloading works

When you call these functions, Mojo picks the correct function to execute based on the number of arguments you passed.

It would be fair to wonder if method and function overloading are the same things.

Method Overloading vs Function Overloading

Note that the main difference between function overloading and method overloading lies in the context in which one uses them. Function overloading applies to standalone functions, while method overloading applies to methods within a class or struct.

Other than that, the concept is essentially the same: it allows you to define multiple pieces of code with the same name but different arguments. Mojo selects the correct one based on the arguments you provide when calling the function or method.

Let’s look at another overloading type now: operator overloading.

Operator Overloading in Mojo Programming Language:

In Mojo, you can also define operator overloading by defining methods with special names. Here we will define a __add__ method to overload the + operator, which will allow us to “add” two rectangles together by creating a new rectangle with the combined length and width:

struct Rectangle:
    var length: F32
    var width: F32

    fn __init__(inout self, length: F32, width: F32) -> None:
        self.length = length
        self.width = width
        print("Rectangle created with length:", self.length, "and width:", self.width)

    fn area(self) -> F32:
        var area: F32 = self.length * self.width
        print("The area of the rectangle is:", area)
        return area
    
    fn area(self, side: F32) -> F32:
        var area: F32 = side * side
        print("The area of the square is:", area)
        return area

    fn perimeter(self) -> F32:
        var perimeter: F32 = 2 * (self.length + self.width)
        print("The perimeter of the rectangle is:", perimeter)
        return perimeter

    fn __add__(self, other: Rectangle) -> Rectangle:
        return Rectangle(self.length + other.length, self.width + other.width)

var rect1 = Rectangle(10.0, 15.0)
var rect2 = Rectangle(5.0, 7.0)
var squareArea: F32 = rect1.area(10.0)
var rect3 = rect1 + rect2

Note that this example also highlights method overloading. Also, do you see that we can add two rectangle objects together even though you couldn’t do that natively? This is due to operator overloading for addition.

intuintuitiveitibve warnings on not using proper var and let

The output above shows that Mojo also provides intuitive warnings that help you optimize your code. I used var instead of let for a variable that never changed in the program. Instead, I should have used let.

Decorators in Mojo Language: What’s the Purpose?

Decorators are a powerful feature in many programming languages that allows you to modify the behavior of a function, method or class without changing its source code. This is known as metaprogramming, since a part of the program tries to modify another part of the program at compile time.

In the context of Mojo programming language, decorators modify the properties and behaviors of types (like structs) and functions.

List of Decorators in Mojo Language:

Here is a brief list of decorators in Mojo language:

  1. @register_passable: This decorator is used to specify that a struct can be passed in a register instead of passing through memory. This can lead to more efficient code.
  2. @register_passable("trivial"): This decorator is a variant of @register_passable for trivial types, indicating that the type is both register passable and has no user-defined copy/move/destroy logic.
  3. @always_inline: This decorator in Mojo language suggests the compiler always inline the decorated function, improving the runtime performance by reducing function call overhead.
  4. @parameter: This decorator is used on nested functions that capture runtime values, creating “parametric” capturing closures.
  5. @value: This decorator is used on structs to automatically generate boilerplate code, such as initializers and copy/move constructors.

Here’s an example below to explain the @value decorator in Mojo:

@value
struct Pet:
    var name: String
    var age: Int

Because of the @value decorator, these boilerplate functions are automatically created for the above struct:

fn __init__(inout self, owned name: String, age: Int):
    self.name = name^
    self.age = age

fn __copyinit__(inout self, existing: Self):
    self.name = existing.name
    self.age = existing.age

fn __moveinit__(inout self, owned existing: Self):
    self.name = existing.name^
    self.age = existing.age

As a result, you can directly write the code below:

from String import String

@value
struct Pet:
    var name: String
    var age: Int

# Creating a new pet
var myCat = Pet("Wia", 6)
print("Original cat name: ", myCat.name)
print("Original cat age: ", myCat.age)
# Copying a pet
var copiedCat = Pet(myCat.name, 7)
print("Copied cat name: ", copiedCat.name)
print("Copied cat age: ", copiedCat.age)
var movedCat = myCat
print("Moved cat name: ", movedCat.name)
print("Moved cat age: ", movedCat.age)

So decorators in Mojo do not only enhance the functionality but also make the Mojo language more intuitive to use. Here is the code output:

using decorators in Mojo to show cat instances and copying and moving pets

Do you see how much more efficient this is? And there’s one more crucial thing to know about Mojo…

Arguments Behavior in Mojo Functions

In programming, when calling a function or method, you use arguments to refer to the actual values on which the function or method operates. Conversely, parameters are the placeholder values within the function or method. When you define a function, you specify the arguments that it will take, along with their types.

Argument Types in Mojo Language:

  • Immutable Arguments (Borrowed):
    • This is the default behavior for fn arguments in Mojo.
    • These are read-only arguments that a function cannot modify.
    • Using borrowed before the argument type is optional; it’s the default behavior.
    • It’s beneficial when passing large or costly values since it prevents unnecessary copies.
@value
struct SomethingBig:
    var id: Int

    fn print_id(self):
        print(self.id)

fn use_something_big(borrowed a: SomethingBig, b: SomethingBig):
   a.print_id()
   b.print_id()

var obj1 = SomethingBig(1)
var obj2 = SomethingBig(2)

use_something_big(obj1, obj2)
immutable arguments Output:

1

2

  • Mutable Arguments (Inout):
    • If a function needs to modify an argument, it can be declared as mutable using the inout keyword.
    • Changes to mutable arguments are visible outside the function.
    • It’s frequently used when a method needs to mutate self.
@value
struct Counter:
    var count: Int

    fn increase(inout self, amount: Int):
        self.count += amount

var myCounter = Counter(0)
print(myCounter.count)
myCounter.increase(3)
print(myCounter.count)
Mutable arguments Output:

0

3

  • Transfer Arguments (Owned and ^):
    • Used for functions that need to take exclusive ownership of a value.
    • Used with the postfixed ^ operator which ends the lifetime of a value binding and transfers the value ownership.
    • Useful when working with unique pointers or when you want to avoid copies.
@value
struct UniqueNumber:
    var num: Int

def use(p: UniqueNumber):
    print(p.num)

def usePointer():
    let ptr = UniqueNumber(100)
    take_ptr(ptr^)

def take_ptr(owned p: UniqueNumber):
    use(p)
    
usePointer()
Transfer Arguments Output:

100

  • Comparison with def arguments:
    • A def argument without an explicit type annotation defaults to Object.
    • A def argument without a convention keyword (such as inout or owned) is passed by implicit copy into a mutable var with the same name as the argument.
def example(inout a: Int, b: Int, c):
   # b and c use value semantics so they're mutable in the function
   ...

fn example(inout a: Int, b_in: Int, c_in: Object):
   # b_in and c_in are immutable references, so we make mutable shadow copies
   var b = b_in
   var c = c_in
   ...

In the next article, we will compare the Mojo language and Python programming language comprehensively and focus on AI development with Mojo language.

Conclusion: The Comprehensive Beginner’s Guide to Mastering Mojo Language

In this article, we introduced Mojo, a new programming language for AI development that aims to blend Python’s functionality with C++’s performance. The language stands out for its dynamic and static typing, modularity, interoperability, and support for both concurrent and distributed computing. With a strong focus on performance and clean, organized codebases, Mojo language facilitates swift, efficient coding.

We also discussed all its key features in-depth, with practical examples. Mojo aims to be a superset of Python– it includes all of its features but adds some new ones. Stay tuned because in the next article, we’ll provide a full comparison between Mojo and Python and explore the potential of Mojo language for AI. If you want to access all the Mojo code used in this article, visit this GitHub link.

More articles on cutting-edge topics: ChatGPT AI: Features to 6X Your Productivity in 2023 and Smart Contracts in Python: Complete Guide

Written by: Syed Umar Bukhari.

]]>
https://sesamedisk.com/mojo-language-101-complete-guide/feed/ 0