Motoko is a modern programming language you can use specifically for Internet Computer programs or as a general-purpose language for programs running on other platforms.
Motoko permits modern programming idioms, including special programming abstractions for distributed applications. Each application consists of an actor that communicates with other actors without using shared state, but instead by using (asynchronous) message passing. The actor-based programming abstractions of Motoko permit human-readable message-passing patterns, and they enforce that each network interaction obeys certain rules and avoids certain common mistakes.
Specifically, Motoko programs are type sound since Motoko includes a practical, modern type system that checks each one before it executes. The Motoko type system statically checks that each Motoko program will execute safely, without internal type errors, on all possible inputs. Consequently, entire classes of common programming pitfalls that are common in other languages are ruled out, including null pointer errors, mis-matched argument and result types and many others.
Motoko provides an actor-based programming model to developers to express server behavior, including that of micro services on the Internet and the Internet Computer.
An actor is similar to an object, but is special in that its isolated state exists remotely, and its interactions with the world are asynchronous.
All communication with and between actors involves passing messages asynchronously over the network using the Internet Computer’s messaging protocol. An actor’s messages are processed in sequence, so state modifications never cause race conditions.
The underlying computing platform provided by the Internet Computer ensures that each message-based synchronization will reach a close, but in doing so each one may also fail due to the usual reasons that arise in distributed systems, including time out.
Like other modern programming languages, Motoko permits an ergonomic syntax for asynchronous communication among components.
In the case of Motoko, each communicating component is an actor.
As an example of using actors, perhaps as an actor ourselves, consider this three-line program:
let result1 = service1.computeAnswer(params); let result2 = service2.computeAnswer(params); finalStep(await result1, await result2)
We can summarize the program’s behavior with three steps:
The program makes two requests (lines 1 and 2) to two distinct services, each implemented internally as an actor (object).
The program waits for each result to be ready (line 3) using the keyword
awaiton each result value.
The program uses both results in the final step (line 3) by calling the
Generally-speaking, the services interleave their executions rather than wait for one another, since doing so reduces overall latency. However, if we try to reduce latency this way without special language support, such interleaving will quickly sacrifice clarity and simplicity.
Even in cases where there are no interleaving executions (for example, if there were only one call above, not two), the programming abstractions still permit clarity and simplicity, for the same reason. Namely, they signal to the compiler where to transform the program, freeing the programmer from contorting the program’s logic in order to interleave its execution with the underlying system’s message-passing loop.
Here, the program uses
await in line 3 to express that interleaving behavior in a simple fashion, with human-readable syntax that is provided by Motoko.
In language settings that lack these abstractions, developers would not merely call these two functions directly, but would instead employ very advanced programming patterns, possibly registering developer-provided “callback functions” within system-provided “event handlers”. Each callback would handle an asynchronous event that arises when an answer is ready. This kind of systems-level programming is powerful, but very error-prone, since it decomposes a high-level data flow into low-level system events that communicate through shared state. Sometimes this style is necessary, but here it is not.
Our program instead eschews that more cumbersome programming style for this more natural, direct style, where each request resembles an ordinary function call. This simpler, stylized programming form has become increasingly popular for expressing practical systems that interact with an external environment, as most modern software does today. However, it requires special compiler and type-system support, as we discuss in more detail below.
In an asynchronous computing setting, a program and its running environment are permitted to perform internal computations that occur concurrently with one another.
Specifically, asynchronous programs are ones where the program’s requests of its environment do not (necessarily) require the program to wait for the environment. In the meantime, the program is permitted to make internal progress within this environment while it waits, perhaps actively. In the example, above, the program makes the second request while still waiting for the first micro service.
Symmetrically, the environment’s requests of the program do not (necessarily) require the environment to wait for the program’s answer to make external progress around it while it waits for this answer.
We do not show an example of this “notify” pattern above, since it uses callbacks (and higher-order functions and control flow) and is thus more complex. However, we discuss a detailed example later in this text.
To address the need for clarity and simplicity, Motoko adopts the increasingly-common program constructs
await, which afford the programmer a structured language for describing potentially-complex asynchronous dependency graphs.
Here, we merely use the ones that arise from calling
await synchronizes on a previously-made promise, and potentially blocks until the future when this promise is fulfilled by its producer.
We show two uses of the
await form in the example above, which await the results of the two services.
When the developer uses these keywords, the compiler transforms the program as necessary, often doing complex transformations to the program’s control- and data-flow that would be tedious to perform by hand. Meanwhile, the type system of Motoko enforces certain correct usage patterns for these constructs, including that types flowing between consumers and producers always agree, and that the types of data sent among services are permitted to flow there, and do not (for example) contain private mutable state.
Like other modern programming languages, Motoko permits each variable to carry the value of a function, object, or a primitive datum (for example, a string, word, or integer). Other types of values exist too, including records, tuples, and “tagged data” called variants.
Motoko enjoys the formal property of type safety, also known as type soundness. We often summarize this idea with the phrase: “Well-typed Motoko programs don’t go wrong”, meaning here that they do not misuse program constructs by treating them as if they have the wrong type.
For example, each variable in a Motoko program carries an associated type, and this type is known statically, before the program executes. Each use of each variable is checked by the compiler to prevent runtime type errors, including null pointer errors.
In this sense, Motoko types provide a form of trusted compiler-verified documentation in the program source code.
As usual, dynamic testing can check properties that are beyond the reach of the Motoko type system. While modern, the Motoko type system is intentionally not “advanced” or exotic in any new ways. Rather, the type system of Motoko integrates lessons from modern, but well-understood, practical type systems of today to provide an approachable, yet mathematically precise language for general-purpose, distributed programming.