Introduction
Some time ago I heard about new nascent pythonic language called Mojo which apart from being faster, according to their website much faster, was also safer. This of course piqued my curiosity, and I decided to have a go implementing a simple design pattern.
Although Mojo is still in its early stages, in this example I am using version 0.7.0, it shows promise. It has the easy syntax of Python combined with a somewhat Rust-like ownership system, and of course type-safety.
The example I am going to present to you is very simple on purpose. First of all I am still learning the language, and yes, the language is not yet finished. Since now windows implementation exists yet, I using mojo on the Windows Subsystem for Linux(WSL)
Introduction to delegation
Delegation is like passing a job to someone else. In Mojo will do this by using composition. It’s almost like asking one object to do a task for another object, just like a subclass asking its parent class for help. Here you can find an article explaining this in more detail.
Implementation in Mojo
Let’s say we want to create a windowing system. We have a basic window that can open and close. We want to add more features to it, like rotating the window.
We will start by defining a Windowable
trait:
trait Windowable:
fn open(self):
...
fn close(self):
...
fn rotate(self,degrees:Int16) raises:
...
Some notes:
- The three dots are part of the syntax, signifying that there is no implementation of the method
- The
rotate()
method has araises
keyword, which means it might raise an exception, for example when the window can not be rotated.
Next we will implement a simple RotatableWindow
:
@value
struct RotatableWindow(Windowable):
var title:String
fn __init__(inout self,title:String):
self.title=title
fn open(borrowed self):
print("Opening window with title "+self.title)
fn close(borrowed self):
print("Closing window with title "+self.title)
fn rotate(borrowed self,degrees: Int16) raises:
self.get_delegate().rotate(degrees)
fn get_delegate(borrowed self)->ExtendedWindow:
return ExtendedWindow(self)
Some notes:
- We have to implement every method in the
Windowable
trait - Rotating the window is delegated to an
ExtendedWindow
struct which we will define later. - The
@value
decorator synthesizes some methods for use, like__copyinit__
giving our struct value types semantics. - Also note that in the trait-methods, we use the
borrowed
keyword, since we are not mutating state. Keywords likeinout
andborrowed
make the code easier to reason about and when used appropiately safer and more stable.
Next we come to the ExtendedWindow
struct:
struct ExtendedWindow(Windowable):
var window:RotatableWindow
fn __init__(inout self,window:RotatableWindow):
self.window=window
fn __copyinit__(inout self,existing:ExtendedWindow):
self.window=existing.window
fn rotate(borrowed self,degrees:Int16) raises:
print("Rotating window by "+str(degrees)+" degrees")
fn open(borrowed self):
self.window.open()
fn close(borrowed self):
self.window.close()
Some remarks here:
- This also implements the
Windowable
trait, however, it delegates many method calles to the underlyingRotatableWindow
instance. - What it does implement itself is the
rotate()
method.
As a complement to our RotatableWindow
struct, we also implement a NonRotatableWindow
struct:
struct NonRotableWindow(Windowable):
var title: String
fn __init__(inout self,title:String):
self.title=title
fn open(borrowed self):
print("Opening window with title "+self.title)
fn close(borrowed self):
print("Closing window with title "+self.title)
fn rotate(borrowed self,degrees: Int16) raises:
raise Error("Can not rotate window with title: "+self.title)
This is more or less the same as the RotatableWindow
with the main difference being the fact that the rotate()
method raises an error.
We also need to define this function, outside of a class:
fn try_to_rotate_window[T:Windowable](w: T,degrees:Int16):
try:
w.rotate(degrees)
except e:
print("Error: "+str(e))
Some notes:
- This method is a generic method, getting an object implementing a
Windowable
trait as one of its parameters. - In the
try-except
block we try to rotate the window, and if it does not succeed we print out an error.
Testing time
Now we can test our setup:
fn main():
let rotatable_window=RotatableWindow("My window")
rotatable_window.open()
try_to_rotate_window(rotatable_window,45)
rotatable_window.close()
let non_rotatable_window=NonRotableWindow("Non rotatable")
non_rotatable_window.open()
try_to_rotate_window(non_rotatable_window,45)
non_rotatable_window.close()
We create both a RotatableWindow
and a NonRotatableWindow
, open them, try to rotate them, and close them again. As you will see in the output, we get an error on the NonRotatableWindow
Conclusion
The Mojo language is still in its early stages, and has some rough edges, which I know will be sorted out if I look at the roadmap. However, implementing this pattern was relatively painless, and quick. The type-safety and traits make the code easy to read.
Furthermore I can say, that although Mojo primary purpose is AI and ML, subjects of great interest to me, I think that it can play an important role in general programming: it has almost the productivity of Python, yet it can compete with Rust when it comes to performance.