Hacker News new | past | comments | ask | show | jobs | submit login
Grain: WebAssembly-First Programming Language (infoq.com)
159 points by todsacerdoti on May 7, 2021 | hide | past | favorite | 70 comments



IMO the video and the home page grain-lang.org dive too quickly into features without giving a compelling answer to "Why should I use Grain?".

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


Consider when you want to use WASM in the first place, when it's not just to pull in legacy code (like from C). You'd want to use it for performance-sensitive portions of code. Then it does make sense to have a well thought out language that doesn't have the baggage from other platforms but integrates well with WASM. Similar to "AssemblyScript", but with less baggage from Javascript.

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.


Everything you listed out is also work going on in Rust as well specifically for targeting WASM, I’m more familiar with Rust, but there are many efforts in other languages too (C, C++, AssemblyScript, etc).

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.


> The question in my mind is do we need a language specialized for this?

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'm just going to guess that a language that is WebAssembly only, *if popular*, would eventually have 1000s of libraries that "just work" vs something like Rust where 99% of the code was never designed to run in a WebAssemebly environment and so probably references things that aren't available in that environment (files, networking, windowing systems, stdin/stdout, etc...)

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.


I don't expect Grain to evolve a huge ecosystem of libraries. A good standard library is certainly possible. The rest can be pulled in via Webassembly and Javascript when necessary.

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.


I’ve been working with Rust and WASM for a little while now. I’ll say that at this point the tools in this area are already very good.

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


The barrier of entry to Rust is large, and I doubt that Rust's WASM integration will be as tight as Grain's just because Rust does a lot of other things.

I'd expect Grain to be easier to learn, and with less distractions from other platforms.


Grain appears to be based on a lot of similar type theory (based on the article). Maybe it will be easier to learn than Rust, maybe not. Maybe there will be fewer tools available and that in and of itself will make it harder to work with.

The question is, will it be worth it to adopt grain instead of a more mainstream language like Rust or AssemblyScript?


It is just a binary with first class support without needed to install the world like Emscripten does.

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.


Yeah I think decluttering the dependency tree is one of the best things we can do in terms of advancing web development. We're finally getting to the point where a sensible web stack might be possible with WASM and WebGPU, but you still mostly need this miles-deep toolchain to build anything for it


> instead of just generating a wasm file and JS bindings

"Just"? I think you are severely underestimating the difficulty of creating and maintaining an additional compiler backend for a programming language.


Not at all, to give as example, Emscripten already depends on node and npm anyway, why use Python for scripting instead of JavaScript?

Or Java for minification instead to npm based tooling for the same purpose?


Emscripten used to use Java for Closure Compiler minification, but no longer does since there are (AOT compiled Java) binaries available for it, for all major platforms. It fetches them using npm. So that part of your post is a little out of date.

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


Well, thanks for duplicating my node, python and java installations.

    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'


> * {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.

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


Indeed. I also don't get from the article or the website what the intention is behind targeting webassembly first, instead of an intermediate that can be compiled to anything.


Grain already contains language features and a standard library which target the webassembly runtime, and maybe even the browser, explicitly.

It wouldn't make a lot of sense to compile the intermediate (which they probably have, anyway) to anything else.


You would enjoy F# which compiles to JS, Wasm, .Net and native


An important question for any WASM language is: what's JS interop like?

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?


I'd say this is more a matter of browsers and WASM specs gradually improving and not something a language/compiler should try to do separately. This is ongoing work and basically there are quite a few things roadmapped/coming that are relevant. As that stuff lands, the entire ecosystem will get more usable.

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.


>Grain is strongly typed (with a typechecker from OCaml)

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.


Most OCaml users I know use emacs with tuareg-mode and merlin (or the equivalent in vim). Once you've got that set up (and it's a breeze these days with opam user-setup) it's super easy to navigate an OCaml code base and access type information on demand.

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.


Why have it 2 strokes away when it can be 0 strokes away? I don't mean this for every definition, just for top-level definitions and class members and methods. I'm a fan of type inference and clean code too, but I also want the code to be documented and human-readable.


in many cases you're probably right that mandatory type annotations would improve readability. Taking the rhetorical question very literally, though, there are some times when having it two keystrokes away is very helpful. I often query the type of a large expression (e.g. a partially applied function to remember what argument comes next) or the signature of some module


After having worked many years with OCaml and a few years with Rust, I prefer the latter's approach of requiring types in function declarations (or equivalently requiring a .mli in OCaml). This makes browsing in code much faster and removes sources of incomprehension.

YMMV


> having to depend on editor support for basic cognition tasks

I'm having horrendous Java flashbacks here, please don't trigger me.


Good OCaml projects almost always have interface files with thorough type annotations and documentation. It's the best of both worlds–the implementation files are free of the clutter of types, and the interfaces are strongly specified.


+1 to this -- good point! It sounds like the legacy project that the parent comment mentioned probably wasn't a "good" project in this respect, though.

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.


Haskell doesn't require top level type definitions.


True, it warns you when compiling with -Wall. I still think it's helpful.


from their website :

>No runtime type errors, ever. Every bit of Grain you write is thoroughly sifted for type errors, with no need for type annotations.


Yes, this is static typing. I wonder though if you have the option of adding type annotations?


I think the easiest way to write webassembly is to write it in webassembly text format:

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.


I was also disappointed that this isn't included in the browsers given that it was designed to be very simple to parse and compile. So I tried as an exercise to build such a compiler[0] and indeed it was much easier than I expected (with a few shortcuts of course, being a POC). It is just 5kb gzipped and it compiles to binary most of the WAT code out there and also quite fast, just a few ms. That said, I think writing WAT by hand is only helpful for very small critical hot code, anything more complex and IMO you need an abstraction of some sort.

[0]: https://github.com/stagas/wel


Wow, all that code is needed to feed a string to WebAssembly.instantiateStreaming()?

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.


You can inline the binary as base64 or simply a Uint8Array and instantiate it without having to fetch a wasm file. Many choose that approach as it also makes the library `import`-able in sync. The code I was pointing can be used to compile the WebAssembly Text format to binary and instantiate it afterwards on-the-fly, which is useful if you don't want a build step, so skipping the tooling(outside the browser) and also make edits which will then be compiled directly when the file executes. It's an option for experimentation mostly.


Ooohh shit! Now I see that instantiateStreaming() does not accept webassembly in text format.

So we need to ship a whole compiler to avoid a build step.

Count me out :)


Yes, my point was that it's so trivial (and negligible in byte count size) to implement a compiler for the Webassembly text format, as the POC demonstrates, that it's curious why the browser isn't including one so that there isn't the need for extra tooling.


>I think the easiest way to write webassembly is to write it in webassembly text format:

It's the simplest, but not the easiest.


We should encourage PL experimentation and welcome new languages!

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!


At first it's really more attractive something for smaller teams building side projects. Maybe also small sub-components.

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.


On the other hand, there are a host of reasons why the barrier to implementing those things has never been lower


Exactly, and with Wasm the barrier to adopt the new language is also way lower as it can be readily combined with modules written in other languages and shares the entire ecosystem.


So this is Reason for WASM? The compiler is written in Reason [1]. This might explain its Ocaml resemblance. Some people here mention Rust, but don't forget that Rust inherited some from OCaml as well (the early version of compiler was written in OCaml [2].)

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/


OCaml is an excellent language but it has kind of undeveloped ecosystem except for one area where the language itself really shines - parsing and generation.


@dang: could we please change the link to the actual website for the language (https://grain-lang.org/)?


I don't know about this suggestion - the linked article gave me a much better idea of the language, and why I would care about it, than the homepage does.


That website was submitted and discussed a couple weeks ago. This submission is actually something new, so I'm glad to see it.


One of the big issues for a WebAssembly-First programming language becoming big is that Rust is already has a very good WebAssembly story, being a direct compiler supported target.

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


My x86_64 and ARM CPUs were also designed to able to run languages like C and C++ (and Rust) efficiently, and so does not have built in garbage collection.


And Rust runs very well on them :)


The burden of entry to learn Rust seems higher than with Grain. And Rust does not have webassembly-centric language features.

C/C++ contains a lot of baggage from other platforms. And has a steeper learning curve. Interoperability with WASM and Javascript is even worse.


Wasm 1.0 did not include garbage collection, but there is a lot of effort right now by many people to add it (that is what I work on myself currently).

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.


Zig supports WebAssembly out of the box, doesn't require GC and has first-class support for WASI.


Rust doesn't have first class WASM support. It lacks many things Emscripten provides to C++. I'd say C++ has first class support for WASM. Very straightforward to get start!!



This looks a bit like rust with garbage collection.

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.


At a quick glance, it looks like OCaml-without-objects (or labels, or polymorphic variants, or GADTs), with better printing. In my books, that's a pretty sweet spot to be:

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


Why? Grain is its own thing, it doesn't need to stand anywhere between Rust, Haskell, or OCaml.


Because every language and tool is part of a wider software ecosystem, and it's reasonable to ask how any given language compares to the other options that you have.


Usually I pick languages based on the ecosystem and not the other way around.

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.


Those code samples make me want to Ctrl+A and "Reformat Code".

I know it's personal and trivial but I struggle to get past niggles like this when first encountering a new tool.


The elevator-pitch is "WASM first language". Which is good, I guess we'll see some unique capabilities here? But we get something that's very close to JavaScript, and with no discernible benefits.

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".


A more reasonable documentation page: https://grain-lang.org/docs/


This looks like a cool and interesting project, but some of the syntax choices seem a little... all over the place to me. It seems like it's drawing quite a bit of inspiration from ML-family languages without fully internalizing _why_ things are the way they are there.

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!


It's just Rust. For some reasons, many new languages these days seem to get inspiration from the Rust syntax. For example, when Rust first came out, people said its choice to use "fn" to refer to functions was awful. Now, it's everywhere. Even Zig is using it.

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.


I don't care so much about keywords like `fn` personally; I was referring more to:

- `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.


Grain has a considerably smaller focus than Rust, and seems to aim for much simpler syntax and development.

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.


R7




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: