[Перевод] У AssemblyScript новый конкурент: язык Grain, созданный для WebAssembly, громко заявил о себе

rkd1in1l-tuwhd4yij2pelhzoh8.png
Оскар Спенсер — один из создателей языка Grain

Grain — язык программирования высокого уровня со строгой типизацией. Это гибридный язык, сочетающий в себе некоторые возможности функционального  (например, вывод типов, сопоставление с образцом, замыкания) и императивного программирования (например, мутабельные переменные).

Оскар Спенсер, один из создателей языка, презентовал Grain на WebAssembly Summit 2021. Спенсер рассказал о его наиболее интересных и важных особенностях, а также подчеркнул, что Grain создан и оптимизирован специально для WebAssembly. Скомпилировать его можно только в байт-код Wasm. По крайней мере, это так на сегодняшний день.

Вот как разработчики сформулировали миссию языка Grain:

Grain стремится взять лучшее от функциональных и императивных языков программирования и донести это до широкой аудитории. Во многих языках реализованы замечательные идеи, но в конечном итоге эти языки не получили широкого распространения. Всё потому, что они слишком странные или чересчур сложные для изучения. И, следовательно, их попытки сплотить вокруг себя большое сообщество провалились. Grain должен вдохнуть новую жизнь в эти идеи и представить их в доступной форме, оставаясь простым в использовании.

WebAssembly Summit — это ежегодная конференция, посвящённая всем аспектам использования и взаимодействия с Wasm. Саммит, о котором идёт речь в статье, прошёл в апреле 2021 года.

Типы данных


Помимо так называемых WebAssembly core data types (например, базовых типов i32 aka Int32), в Grain есть составные типы данных. Например:  
  • Option — специальный тип перечислений, определяющий наличие вариантов (опций) как Some или отсутствие как None;  
  • Result — всё стандартно: Err, если что-то пошло не так, и Ok, если всё так;  
  • Stack — иммутабельный стек. 

В Grain также реализованы кортежи (tuples), записи (records), массивы (arrays), списки (lists), диапазоны (ranges), символьный тип (characters), строки (strings), множества (sets), коллекции (maps), очереди (queues) и многое другое.

Вот так выглядит простейшая программа на Grain:

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

Пример взят по тайм-коду из видео доклада Спенсера.

Обобщённые конструкторы


Grain позволяет, например, создавать перечисления с помощью обобщённых конструкторов.
enum  Veggie {  Squash,  Cabbage,  Broccoli  }  

enum  Fruit {  Apples,  Oranges,  Bananas  }  

enum  Inventory  {  Crate(produce),  Truckload(produce)  }  

let veggieInventory  =  [Crate(Broccoli),  Truckload(Cabbage)]  

let fruitInventory  =  [Crate(Apples),  Truckload(Oranges)]

В приведённом выше примере кода параметром конструкторов Crate и Truckload служит переменная типа produce.

Сопоставление с образцом

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

}

В разделе документации Bindings in Match Patterns говорится, что мы можем привязать шаблон соответствия (образец), например, к имени перечисления. Тогда, подставив в теле выражения match его имя в соответствующем регистре, мы можем автоматически проверять значения этого перечисления.

В приведённом выше примере кода значение переменной topping биндится с перечислением Topping, которое одним из своих значений инициализирует элемент в перечислении типа Menu.

Сопоставление с образцом работает также для записей, кортежей и списков. Рассмотрим пример для списка:

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

}

Подробнее об этой возможности языка можно прочитать здесь

Коротко о других возможностях Grain


В процессе работы над Grain разработчики уделяли большое внимание полноценной и современной реализации функций. Например, функцию можно использовать как значение. Так же, как и в JavaScript, в Grain реализованы замыкания.

Ещё Grain знает, как печатать значения без преобразования в строку. Кроме того, Grain может обращаться к системному интерфейсу WebAssembly (WASI). WebAssembly System Interface должен позволить запускать Wasm-код на всех устройствах и операционных системах. WASI включает API для асинхронного ввода-вывода, генерации случайных чисел, получения текущего времени и многого другого.

Программы Grain можно делить на модули. Модули можно экспортировать или импортировать из других модулей Grain. Модули Grain также могут подключать внешние функции. Но только при условии, что разработчик сам проследит за тем, чтобы соответствующие внешние функции были созданы.

Сейчас идёт активное допиливание интерфейса внешних функций (FFI — Foreign function interface), а также статического связывания, работы в 64-битном режиме, стандартной DOM-библиотеки и макросов.

Для тех, кто хочет погрузиться глубже


Статическое связывание


До апрельского релиза 2021 года все программы Grain собирал специальный js-движок, игравший роль динамического линкера. Однако, при таком подходе программы можно было запускать только в Node.js и браузерах. Теперь Grain  получил возможность использовать автономные среды выполнения для WebAssembly — Wasmer, Wasmtime и Wasm3.

Ещё в 2018 году разработчики Grain ясно осознали, что статическое связывание жизненно важно для дальнейшего развития проекта. Но они надеялись, что смогут использовать для этого инструменты экосистемы. К сожалению, не нашлось ни одного подходящего инструмента, и Оскар решил встроить фазу статической компоновки прямо в компилятор Grain. Чтобы реализовать статическое связывание с использованием инструментария Binaryen, потребовалось написать примерно 600 строк кода. 

Однако, после этого пришлось заняться более сложной и объёмной задачей — переписать JavaScript-код среды выполнения и AssemblyScript-код стандартной библиотеки на чистом Grain.

Grain vs AssemblyScript


AssemblyScript, который компилирует подмножество языка TypeScript в Wasm, также описывают как специально созданный язык для WebAssembly. Он также имеет стандартную библиотеку с составными типами (например, для массивов или даты). Как и Grain, AssemblyScript компилируется в WebAssembly с использованием Binaryen. AssemblyScript, будучи языком высокого уровня, призван дать разработчикам больше «низкоуровневых» возможностей при решении требовательных к производительности задач (по сравнению с теми же TS и JS). 

Однако AssemblyScript требует дополнительных аннотаций типов. По сравнению с TypeScript, вывод типа в AssemblyScript ограничен, поскольку тип каждого выражения должен быть известен заранее. Это означает, что объявления переменных и параметров должны иметь аннотированный тип или инициализатор.

Grain — язык со строгой типизацией (её обеспечивает OCaml typechecker), который практически не требует использования аннотаций. Добиться этого удалось благодаря тому, что разработчики реализовали механизм вывода типов. 

Где искать?


Grain (вместе с CLI, компилятором, средой выполнения и стандартной библиотекой) поставляется в одном бинарнике. Эта версия доступна для MacOS x64, Linux x64, и Windows x64. На других платформах можно использовать JS-версию компилятора Grain.

Документация Grain.

Видео с докладом:


Облачные серверы от Маклауд быстрые и безопасные.

Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!

et1aypandyuamqprsz3m2ntm4ky.png

© Habrahabr.ru