Grain: WebAssembly-First Programming Language
infoq.com
infoq.com
"Powered by WebAssembly": Other languages can compile to WebAssembly as well as other targets. Why is limiting to only one target considered a good thing? What does it buy you? (if it's unique that it has a GC, or if the GC is performant that may be a good reason. Advertise it!).
The vision of bringing ideas from functional/academic languages into the mainstream is noble. But the language features seem very run of the mill? I don't understand what it's referring to.
Comments on the standard library:
* Reference docs should make more clear the differences between Array and List. I assume Array is a fixed size contiguous array of data with constant time get and set? And List is an immutable linked list. But I had to wander around docs and implementation to reach that conclusion.
* Reference docs should probably mention List syntax in the List docs. Ditto for Arrays. You normally expect to see that at the top of the page, vs as a surprising comment "An alias for normal syntactic array access, i.e. array[n].". It's hard to tell what is a library feature vs primitive to the language.
* {Array,List}.append: In most languages I have used append means to append a single element (or in Go's case it is variadic). I would probably rename this to concat and remove the existing concat function.
* What is the benefit to having a separate Stack type vs adding convenience functions to List?
let List.push = (value, list) => list.
let List.pop = (list) => (value, list).I still don't get why so many take the path of installing npm libraries, Python scripts, Java based closure compiler, LLVM based binaren, instead of just generating a wasm file and JS bindings.
Yes I know Grain depends on binaren, just making the point about the dependencies.
"Just"? I think you are severely underestimating the difficulty of creating and maintaining an additional compiler backend for a programming language.
Or Java for minification instead to npm based tooling for the same purpose?
(As to why use Java at all, Closure is written in it, and its advanced optimizations make a huge difference!)
The use of both node and python in Emscripten is slightly redundant. When Emscripten started, node could not yet replace python for what we do with it. Today, if someone wants to refactor that code to node we'd definitely be interested in such a PR! But overall such a refactoring has been lower in priority compared to other work (new wasm features, performance, etc.).
D:\wasm\emsdk>emsdk install latest
Installing SDK 'sdk-releases-upstream-e0c15cd14170f407a9eb27fcbad22931dc67feb7-64bit'..
Installing tool 'node-14.15.5-64bit'..
Downloading: D:/wasm/emsdk/zips/node-v14.15.5-win-x64.zip from https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/node-v14.15.5-win-x64.zip, 30284821 Bytes
Unpacking 'D:/wasm/emsdk/zips/node-v14.15.5-win-x64.zip' to 'D:/wasm/emsdk/node/14.15.5_64bit'
Done installing tool 'node-14.15.5-64bit'.
Installing tool 'python-3.9.2-1-64bit'..
Downloading: D:/wasm/emsdk/zips/python-3.9.2-1-embed-amd64+pywin32.zip from https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/python-3.9.2-1-embed-amd64+pywin32.zip, 16982397 Bytes
Unpacking 'D:/wasm/emsdk/zips/python-3.9.2-1-embed-amd64+pywin32.zip' to 'D:/wasm/emsdk/python/3.9.2-1_64bit'
Done installing tool 'python-3.9.2-1-64bit'.
Installing tool 'java-8.152-64bit'..
Downloading: D:/wasm/emsdk/zips/portable_jre_8_update_152_64bit.zip from https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/portable_jre_8_update_152_64bit.zip, 69241499 Bytes
Unpacking 'D:/wasm/emsdk/zips/portable_jre_8_update_152_64bit.zip' to 'D:/wasm/emsdk/java/8.152_64bit'
Done installing tool 'java-8.152-64bit'.
Installing tool 'releases-upstream-e0c15cd14170f407a9eb27fcbad22931dc67feb7-64bit'..
Downloading: D:/wasm/emsdk/zips/e0c15cd14170f407a9eb27fcbad22931dc67feb7-wasm-binaries.zip from https://storage.googleapis.com/webassembly/emscripten-releases-builds/win/e0c15cd14170f407a9eb27fcbad22931dc67feb7/wasm-binaries.zip, 433852068 Bytes
Unpacking 'D:/wasm/emsdk/zips/e0c15cd14170f407a9eb27fcbad22931dc67feb7-wasm-binaries.zip' to 'D:/wasm/emsdk/upstream'
Done installing tool 'releases-upstream-e0c15cd14170f407a9eb27fcbad22931dc67feb7-64bit'.
Done installing SDK 'sdk-releases-upstream-e0c15cd14170f407a9eb27fcbad22931dc67feb7-64bit'It wouldn't make a lot of sense to compile the intermediate (which they probably have, anyway) to anything else.
The other advantage of WASM is that it makes Javascript and its runtime get out of the way, enabling Grain to develop its own runtime and standard library. Then you could write a web application (or increasingly other things, too) in Grain, leveraging the language features. The appeal there would be less about performance and more about a more pleasant experience than you'd get with Javascript or Typescript.
That's what I understood. I haven't even tried Grain yet.
The question in my mind is do we need a language specialized for this? For example, a lot of the design choices of the language, like OCAML inspiration, good FFI are already part of other languages.
While to me that means it will probably be a niche language, the fact that it’s specifically targeting only WASM for now, will probably mean it will be a great language for experimentation that other languages can learn from.
Based on my experience writing wasm toolchains, I think specialized languages can have big benefits.
Existing languages always have downsides on the web and in wasm. The downsides are worth it when porting existing code, and often for other reasons, but a new toolchain can avoid them.
A specialized language can design its library for wasm, avoiding unnecessary code, like e.g. string formatting and other libc things in C/C++/Rust (unless you are very careful). Almost no language was designed for emitting small binaries like wasm wants - wasm toolchains for them work hard on this, but there is always some friction.
A specialized language can have async APIs like the web requires, avoiding workarounds there. And it can more easily benefit from new wasm features in some cases, like GC: there is work for that in LLVM, which will give some support for C/C++/Rust, but it will be limited - while I'd expect Grain and AssemblyScript etc. to much more easily and greatly benefit.
Of course specialized languages have downsides too, like limited or less efficient non-wasm ports. There is room for both!
I didn't look at Grain. I'm just thinking out loud what the benefits of a WebAssembly first language could be. It's not so much the language itself though.
It will probably make sense to use Grain along with React or Vue.js, for example, just because anything else will just not be worth the trouble.
Most libraries that implement algorithms and data structures just work. It starts getting messy when things need access to the file system (or other environmental things) but that’s fairly obvious, and what would that be used for in a web context?
WASM already has WASI which is pretty well supported in Rust today, and works well for any POSIX like needs.
In terms of using with React or Vue, I expect no matter the language, the incorporation of WASM modules will be identical (the tooling is where things will set themselves apart).
I'd expect Grain to be easier to learn, and with less distractions from other platforms.
The question is, will it be worth it to adopt grain instead of a more mainstream language like Rust or AssemblyScript?
Grain is written in Reason, which uses append and concat, so this is probably why Grain does: https://reasonml.github.io/docs/en/basic-structures#concaten...
The raw interop features are extremely primitive, so you really need something ergonomic built on top of them. Rust's wasm-bindgen combined with some Traits shenanigans gets you something decent. A language designed specifically for WASM seems like the perfect opportunity to provide an exceptional story for this, but I don't see anything about it in the OP (only skimmed so could have missed it). Does Grain have a story here?
JS interop is short term a bit annoying, depending on what you are doing. But at this point plenty of non trivial things are being done with WASM that are finding their way into e.g. Figma, or bits and pieces of popular frameworks like react or web frameworks in Rust or other languages that manipulate the DOM, do things with WebGL, etc. So, yes, there is room for improvement but it's not completely horribly unusable right now.
I tried working on a complex OCaml project where the author had written as little type annotations as possible. It was a horrendous experience because it was impossible to determine the type of most variables by reading the code. I had to use a language server to obtain type information and I had to consult it very often. I hope they don't proliferate this nonsense and instead require type annotations for top-level definitions like Haskell does.
>No runtime type errors, ever. Every bit of Grain you write is thoroughly sifted for type errors, with no need for type annotations.
This is a feature, not a bug -- getting the type of an arbitrary expression is two keystrokes away, but there's no visual clutter from excessive type annotations. And the tooling goes well beyond this one feature.
Working in OCaml without merlin+tuareg feels like having a hand tied behind your back (to me), so I feel your pain. This certainly raises the barrier to entry slightly, but I think it's worth it for the convenience and ergonomics once you're past that initial barrier.
I'm having horrendous Java flashbacks here, please don't trigger me.
YMMV
This has a number of advantages vs. the `export` syntax used in Grain, too. Being able to look at one `.mli` file for a well-documented public interface is much easier than looking through a source file for what's been exported and what hasn't.
https://developer.mozilla.org/en-US/docs/WebAssembly/Underst...
And then load that code via
WebAssembly.instantiateStreaming(fetch('mycode.wasm'))
It would be even nicer if it could be done without a pass through the network. But I think there is nothing built into browsers to support inline webassembly.What does the code do?
I was hoping there could be a trick like feeding a "blob:..." url to instantiateStreaming() or something.
Since the bottleneck of a codebase typically is a single loop, I think it often would be a good approach to rewrite that one in Webassembly by hand.
So we need to ship a whole compiler to avoid a build step.
Count me out :)
It's the simplest, but not the easiest.
But keep in mind that cost of entry for production usage these days is a full suite of tooling (package manager, code formatter, language server for IDEs, etc).
I didn't see a standout feature in this article on Grain that made me think the manpower investment in developing the language and tooling is worth it, but I could be wrong!
When dealing with TypeScript and the usual suspects like React + Redux, or AngularJS and its ilk, what bothers me is all the boilerplate and complexity. TypeScript contains a lot of baggage from Javascript.
A functional, well featured language that doesn't require a humongous runtime as a download (like Python on WASM, which works, but like 40mb to get you started...) might be an attractive solution there.
Some say OCaml is a language for writing a language. I thought it was a joke, but maybe that's true for good reasons. (Some compiler textbooks use ML too [3].)
[1] https://reasonml.github.io/ [2] https://en.wikipedia.org/wiki/Rust_(programming_language)#Hi... [3] https://www.cs.princeton.edu/~appel/modern/
In addition, WebAssembly was designed to be able to run languages like C and C++ (and Rust) efficiently, and so does not have built in garbage collection.
Thus a language that wants to be WebAssembly first, is either going to have to bring in a garbage collector, have safety with something very similar to Rust's borrow checker (in that case, it would be similar complexity to Rust), or else put the burden on the programmer for safety like C and C++ (in that case, what is the advantage over them).
C/C++ contains a lot of baggage from other platforms. And has a steeper learning curve. Interoperability with WASM and Javascript is even worse.
Once wasm has GC, languages like Grain will be able to save a lot on code size, and have some advantages over the "first set" of wasm languages (C/C++/Rust/Zig/etc., all using linear memory), like smaller sizes, less memory fragmentation, and properly collecting cycles with JS.
And 3 years ago: https://news.ycombinator.com/item?id=17645004
I see it says it has a type checker or inference from OCaml. I’m guessing this means it doesn’t natively support higher kinded types.
Don’t get me wrong - I love new languages and I commend the author(s)! But I would like to know where it stands between Rust, Haskell and Ocaml.
One thing that Grain has going for itself is the toolchain, just like AssemblyScript.
Most languages targeting WebAssembly, instead of directly generating wasm files, use a klundge of Python, JS and Java tools, with Emscripten being the most notorious in that regard.
- stronger typing than TypeScript/AssemblyScript, while being similarly flexible;
- simpler and more gc-ed than Rust.
Of course, no ecosystem yet afaict.
Next time I start a project that targets wasm, I'll definitely look at Grain.
I know it's personal and trivial but I struggle to get past niggles like this when first encountering a new tool.
So in a way, while I commend the project on shipping, the unfortunately conclusion here is rather "there's no reason to use WASM-first languages".
Of course, that's all "just syntax" and not the end of the world.
Reason targets a similar use case* but, to me, makes a more sane set of syntactic choices and tradeoffs, while also having access to the whole OCaml ecosystem. I'm not sure why one would reach for Grain over that, though maybe I'm just not the target audience here.
* i.e. developers who want type safety, good inference, a familiar JS-ish syntax, and to target the web
edit: I see now that the compiler is built in Reason -- I'd imagine they've thought about these things and made considered choices on syntax!
One of the reasons would be that Rust already made some reasonable choices regarding how to incorporate ML features into the C++-eseque syntax, so you don't have to worry about it. But it is indeed a bit sad that a more easy to use C++-eseque syntax that is substantially different from Rust isn't coming out nowadays.
- `let` in a statement language (as opposed to let-binding expressions)
- comma-separating patterns (as opposed to using pipes to mimic BNF, also potentially causing confusion vs. tuple/record constructors)
- whitespace rather than semicolons for sequencing (though I'd imagine others might disagree on that front).
Some of that overlaps with Rust, to be sure -- I didn't pick up on those similarities at first.
When targeting Webassembly with Rust, there is still a lot in the Rust ecosystem that can get in your way, and not a lot in the standard language that will help you, either.