Oscar Spencer recently presented Grain, a new strongly-typed, high-level language that compiles to WebAssembly. Grain includes functional programming features (e.g., type inference, pattern matching, closures) while allowing mutable variables. Grain also has a standard library with composite data structures (Option, Stack, Result) and system calls (e.g., I/O, process handling).
In a talk at the WebAssembly Summit 2021, Spencer went through the main characteristics of Grain, a programming language that he said is made exclusively for WebAssembly, its only compile target.
The Grain language self-describes its mission as follows:
Grain aims to modernize innovative features from functional and academic programming languages and bring them to the masses. Many languages have had wonderful ideas, but they have ultimately been dismissed as esoteric or too difficult to learn and, consequently, have struggled to rally a large community around them. Grain hopes to bring new life to these ideas and present them in an accessible form that’s easy to use and understand.
Grain is strongly typed (with a typechecker from OCaml), and its type inference significantly reduces the need for type annotations. Besides the WebAssembly core data types (e.g., i32
becomes Int32
), Grain also provides composite types that are commonly used in high-level typed languages. The Option
type represents the possibility of something being present (with the Some
variant), or not (with the None
variant). The Result
type represents a success case (with the Ok
variant), or an error case (with the Err
variant). The Stack
type represents an immutable stack. Additional types and language syntax support tuples, records, arrays, lists, ranges, characters, strings, sets, maps, queues, and more.
Developers may define Enum
data types with generic data constructors:
enum Veggie { Squash, Cabbage, Broccoli }
enum Fruit { Apples, Oranges, Bananas }
enum Inventory<produce> { Crate(produce), Truckload(produce) }
let veggieInventory = [Crate(Broccoli), Truckload(Cabbage)]
let fruitInventory = [Crate(Apples), Truckload(Oranges)]
In the previous code, the data constructor Crate
of the Inventory
enum type is parameterized by the produce
type.
Grain developers can use pattern matching on Enum
data types:
enum Topping { Cheese, Pepperoni, Peppers, Pineapple }
enum Menu { Pizza(Topping), Calzone(Topping) }
let item = Calzone(Peppers)
match (item) {
Calzone(topping) => {
if (checkSpecials(topping)) {
print("These are half off this week.")
} else {
print("No current specials.")
}
},
_ => print("No current specials.")
}
In the previous code, topping
is bound to the value of type Topping
used to construct item
of type Menu
.
Pattern matching can also be done on records, tuples, and lists:
let list = [1, 2, 3]
match (list) {
[] => print("List contains no elements"),
[_] => print("List contains one element"),
[_, _] => print("List contains two elements"),
[_, _, _] => print("List contains three elements"),
_ => print("List containes more than 3 elements")
}
Functions are a first-class concept in Grain, i.e. functions can be consumed as values. Functions can recursively call themselves. Like in JavaScript, functions are also able to keep a reference of values within their scope (closure).
Grain understands how to print values without developers having to define string representation for values. Grain additionally provides system calls that align with those defined in the WebAssembly System Interface (WASI). WASI includes APIs for asynchronous I/O, random number generation, access to the current time, and more.
Grain programs can be split into modules that can import from —or export to— other Grain modules. Grain modules can also import from foreign functions, provided developers explicitly type the foreign function.
Spencer illustrated the type of high-level programming that Grain enables with a simple example:
Future Grain development should include a better foreign function interface, static linking, support for 64-bit mode, a DOM standard library, macros, and more.
AssemblyScript, which compiles a strict variant of TypeScript to WebAssembly, also self-describes as made for WebAssembly and includes a standard library with common composite types (e.g., arrays, dates). Like Grain, AssemblyScript compiles to WebAssembly using Binaryen. AssemblyScript strives to give developers low-level control within a higher-level language while remaining mindful of code size. AssemblyScript however may require more type annotations from developers:
Compared to TypeScript, type inference in AssemblyScript is limited because the type of each expression must be known in advance. This means that variable and parameter declarations must either have their type annotated or have an initializer.
The Grain toolchain (e.g., CLI, compiler, runtime, and standard library) is shipped as a single binary. Binaries are available for MacOS x64, Linux x64, and Windows x64. A JavaScript version of the Grain compiler is available for use in platforms where a custom binary is not available.
The full talk is available online and contains slides, code samples, and additional detailed explanations. The WebAssembly Summit is a yearly conference about all things WebAssembly. The WebAssembly Summit took place online in April 2021.