AssemblyScript has a new competitor: the Grain language, created for WebAssembly, loudly declared itself



Oscar Spencer is one of the founders of the Grain



Grain language, a highly typed high-level programming language. It is a hybrid language that combines some features of functional (for example, type inference, pattern matching, closures) and imperative programming (for example, mutable variables).



Oscar Spencer, one of the creators of the language, presented Grain at the WebAssembly Summit 2021. Spencer spoke about its most interesting and important features, and also emphasized that Grain was created and optimized specifically for WebAssembly. It can only be compiled into Wasm bytecode. At least this is the case today.



This is how the developers formulated the mission of the Grain language:



Grain strives to take the best of functional and imperative programming languages ​​and bring that to a wider audience. Many languages ​​have great ideas, but ultimately these languages ​​did not become widespread. This is because they are too weird or too difficult to learn. And consequently, their attempts to rally a large community around them have failed. Grain should breathe new life into these ideas and present them in an accessible form, while remaining easy to use.


The WebAssembly Summit is an annual conference dedicated to all aspects of using and interacting with Wasm. The summit discussed in the article took place in April 2021.



Data types



In addition to the so-called WebAssembly core data types (for example, basic types i32 aka Int32), Grain has composite data types. For example: 



  • Option is a special type of enumeration that defines the presence of options (options) as Some or the absence as None; 
  • Result - everything is standard: Err, if something went wrong, and Ok, if everything is so; 
  • Stack is an immutable stack. 


Grain also implements tuples, records, arrays, lists, ranges, characters, strings, sets, maps, queues and much more.



This is how the simplest Grain program looks like:



import List from «list»

import Option from «option»

import { data } from «./data»

let allPairs = List.product(data, data);

let (a, b) = Option.expect (

	«Expected to find values»,

	List.find(((a ,b)) => a + b == 2020, allPairs)

);

print((«Values», a, b));

print((«Answer», a * b));

      
      





The example is taken from the timecode from Spencer 's video report.



Generic Constructors



Grain allows, for example, to create enums using generic 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 above code example, the parameter of the Crate and Truckload constructors is a produce variable.



Pattern matching



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.»)  

}

      
      





The documentation section Bindings in Match Patterns says that we can bind a matching pattern (pattern) to, for example, an enumeration name. Then, substituting its name in the corresponding case in the body of the match expression, we can automatically check the values ​​of this enumeration.



In the above code example, the value of the topping variable is bound with the Topping enumeration, which initializes the element in the Menu enumeration with one of its values.



Pattern matching also works for records, tuples, and lists. Let's look at an example for a list:



let list  =  [123

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»)  

}

      
      





You can read more about this language feature here.



Briefly about other possibilities of Grain



While working on Grain, the developers paid great attention to the full and modern implementation of functions. For example, a function can be used as a value. Just like JavaScript, Grain implements closures.



Grain also knows how to print values ​​without converting them to a string. Additionally, Grain can access the WebAssembly System Interface (WASI). The WebAssembly System Interface should allow Wasm code to run on all devices and operating systems. WASI includes APIs for asynchronous I / O, random number generation, getting the current time, and more.



Grain programs can be divided into modules. Modules can be exported or imported from other Grain modules. Grain modules can also connect external functions. But only on condition that the developer himself makes sure that the corresponding external functions are created.



Now there is an active completion of the foreign function interface (FFI - Foreign function interface), as well as static linking, working in 64-bit mode, the standard DOM library and macros.



For those who want to dive deeper



Static linking



Until the April 2021 release, all Grain programs were assembled by a special js engine that played the role of a dynamic linker. However, with this approach, programs could only be run in Node.js and browsers. Grain is now able to use stand-alone runtimes for WebAssembly - Wasmer, Wasmtime, and Wasm3.



Back in 2018, the Grain developers clearly realized that static linking is vital to the future development of the project. But they hoped they could use the ecosystem tools to do this. Unfortunately, no suitable tool was found, and Oscar decided to embed the static link phase directly into the Grain compiler. To implement static linking using the Binaryen toolkit, it took about 600 lines of code to write. 



However, after that I had to tackle a more complex and voluminous task - to rewrite the JavaScript code of the runtime and AssemblyScript code of the standard library in pure Grain.



Grain vs AssemblyScript



AssemblyScript , which compiles a subset of the TypeScript language to Wasm, is also described as a specially crafted language for WebAssembly. It also has a standard library with composite types (for example for arrays or dates). Like Grain, AssemblyScript is compiled to WebAssembly using Binaryen. AssemblyScript, being a high-level language, aims to give developers more "low-level" capabilities when solving performance-demanding tasks (compared to TS and JS). 



AssemblyScript however requires additional annotationstypes. 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 have an annotated type or initializer.



Grain is a strongly typed language (provided by OCaml typechecker) that requires little or no annotations. This was achieved due to the fact that the developers implemented a type inference mechanism. 



Where to looking for?



Grain (along with the CLI, compiler, runtime, and standard library) comes in a single binary. This version is available for MacOS x64 , Linux x64 , and Windows x64 . On other platforms, you can use the JS version of the Grain compiler. Grain



Documentation .



Video with the report:










Cloud servers from Macleod are fast and secure.



Register using the link above or by clicking on the banner and get a 10% discount for the first month of renting a server of any configuration!






All Articles