top | item 17645004

Grain: A strongly-typed functional programming language for the modern web

259 points| bpierre | 7 years ago |grain-lang.org

153 comments

order
[+] tlrobinson|7 years ago|reply
The main page doesn't do a great job of showing what's interesting about Grain vs. JavaScript. The only hint is this: "No runtime exceptions, ever. Every bit of Grain you write is thoroughly sifted for type errors, with no need for any type annotations."

Maybe show some examples of errors Grain would catch that JavaScript wouldn't, like Elm does: http://elm-lang.org/

[+] repsilat|7 years ago|reply
> No runtime exceptions, ever.

This is something I wonder about JS: there are a lot of places that exceptions happen in (say) Python that just return `NaN` or `undefined` in javascript. Is it intentional? Is it a good idea?

Examples: the multiplication operator essentially never throws. Out-of-bounds (or "not found") lookups don't throw.

I suspect the logic is "only throw if you have the wrong type for that operation" -- like calling something that isn't a function, or looking up an attribute on something that isn't an object. (Except multiplication for things that aren't numbers is fine, I guess...)

I'm writing a dynamically typed language and don't know if it's better to go the JS way or the Python way. Or do something horrible like having multiplication return a `Maybe` type...

[+] thangngoc89|7 years ago|reply
I think the marketing page is not doing a great job. Grain is a language compiled to WebAssembly. So comparing it with either Javascript or Elm is pointless
[+] mcintyre1994|7 years ago|reply
> No runtime exceptions, ever.

I'm curious how they handle JSON parsing. For example in Scala/Play you define the class you want to parse to, and if the JSON passed in at runtime doesn't match the shape of that class then you get a runtime exception. Obviously it'd be nice if that didn't happen, but I can't think of any alternative that would make sense.

[+] tomp|7 years ago|reply
I wonder how they handle array out-of-bounds accesses...
[+] thefounder|7 years ago|reply
I don't think web languages should have comparisons with JS on their homepage now that we have wasm.
[+] rzwitserloot|7 years ago|reply
Pretty much the first sentence on the page:

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

That's... weird to me. That seems to posit that ALL runtime exceptions are necessarily type errors. Huh?

What about full disk, DB errors, data verification error, parsing errors, network errors, and tons of other errors that don't appear to be type system related?

[A] This language only doesn't realize this class of errors exist, which, um, seriously? That can't bode well.

[B] The language uses the type system to propagate such errors. `Either<Result, ErrorCode>` for example. The language is somewhat unique in that it is designed to enforce such code style for ALL possible error conditions.

[C] The language returns null or blank objects on error conditions, such as how (early) javascript returns 'NaN' when trying to parse "hello!" as a number, instead of raising an error condition. That's a style choice I really wouldn't like, but I guess to each their own.

[+] oscarspen|7 years ago|reply
Grain dev here! Fixed that on the site. That line was just supposed to be about runtime type errors, not all errors.

Grain is definitely still really in its alpha form. There's lots we want to do with it that we're still getting around to! We weren't quite expecting much of any traffic to the site, but that's really my fault for having the site be public before we were at least a bit further along. :)

Even still, it's been awesome to get a lot of early feedback! We'll get a roadmap together so it's easier for people to see where we're trying to take the language.

[+] colanderman|7 years ago|reply
I would assume case B, which isn't really that unique. Erlang for example follows that pattern (and has no concept of "exception"). After all, I/O errors, parsing errors, etc., aren't exceptional; they very much should be expected. Forcing them to be handled in the normal code path helps create robust systems.

(There are of course also true runtime errors -- e.g. out-of-memory -- and precondition errors -- i.e., coding errors that fall outside the capability of the type system to enforce -- which do not fit this pattern.)

[+] leshow|7 years ago|reply
The likely reason is B. Elm is a frontend web language that also has this guarantee, and it uses Either types for situations that can fail. It's not entirely unique in that regard, there are other languages (mostly) without exceptions; Rust, for example.
[+] atombender|7 years ago|reply
Errors could presumably be provided as return values (similar to Rust's Option or Haskell's Either), though we know from languages like Go that propagating these manually is a chore and an eyesore.

A harder problem is errors that cannot be handled by a simpler type system. For example, consider Haskell's head function, which is defined like this:

    head :: [a] -> a
In other words, it takes a list, and returns the first element. But if the list is empty, it throws an exception (technically called an "error" in Haskell):

    > head []
    *** Exception: Prelude.head: empty list
Haskell's type system is extremely powerful, but it cannot catch this at compile time.

In order to express constraints such as "list must not be empty" or "number must be higher than 1" or "file must be open, the type system needs to understand the semantics of the values expressed by a type system. This can be solved by dependent typing, but that's a complicated concept currently implemented by only a handful of languages (e.g. Idris).

[+] tomp|7 years ago|reply
This is actually a really complex problem. Recently in C++, the discussion is happening regarding `nothrow` functions. Can such functions allocate memory (and hence throw "out-of-memory" errors)?

The basic tradeoff is, most program can't (or don't) handle an "out-of-memory" error anyways, so for those, "nothrow" and "nothrow+allocates" is essentially the same. On the other hand, some (few) software is written to handle "out-of-memory" errors, and C++ is one of the few languages that targets such software, so in those programs, the distinction is meaningful.

[+] anonytrary|7 years ago|reply
I mean, this looks interesting, but why was this posted prematurely? Now I have to remember to look this up again in two months when they actually have a website that isn't 3% done. Basically all of the docs are in the "todo" stage. I always get a bit annoyed when people post their pages way too early.
[+] rkangel|7 years ago|reply
"If you are not embarrassed by the first version of your product, you've launched too late." -Reid Hoffman

In this case, publicising, and getting more contributors can help fix these problems.

[+] jholman|7 years ago|reply
What makes you think that the HN poster has any affiliation with the project, or vice versa?
[+] mirekrusin|7 years ago|reply
Maybe they prefer to focus on writing actual language than PR’ish stuff? It’s quite refreshing after seeing too much of the opposite (especially in blockchain space where you have in 99% of cases just pretty PR page and zero product/code).

I’m sure they’re open and would be happy to see contributions.

Translating source code into docs is a great way to learn and it looks like very pleasant language to read.

[+] Tade0|7 years ago|reply
I have a distrust for languages that advertise themselves as "strongly-typed" or "bringing sanity to the front-end" - the former doesn't have a single clear definition and the latter - while not the case here - is questionable in it's own right.
[+] truncate|7 years ago|reply
How does do handle memory management? If I'm not mistaken, in webassembly programs use a fixed buffer to access memory, which means you need some runtime support to manage that, or apply some kind of technique like this[1].

[1] http://home.pipeline.com/~hbaker1/CheneyMTA.html

[+] munificent|7 years ago|reply
It appears to allocate memory but never free it:

https://github.com/grain-lang/grain/blob/master/runtime/src/...

A lot of new languages start out this way, where they just assume memory is infinite and then eventually add the GC later. That's a really nasty ball of technical debt to be sitting on, and I've seen nascent language implementations die because they couldn't get past it.

[+] nine_k|7 years ago|reply
Since it's pretty ML-flavored, a comparison with Reason [1] would be nice.

I see compiling to WASM as the key differentiator now. This likely means that all the mechanics required for an ML-style language, like garbage collection, must be included in a runtime library.

[1]: https://reasonml.github.io/

[+] wffurr|7 years ago|reply
Only until the WebAssembly spec included access to the host GC:

>>> there's already a very efficient, solidly tested, constantly improving garbage collector in your browser that uses all the possible dirty low-level tricks known to mankind, which is the GC being used for JavaScript. What if we could give you access to the garbage collector directly?

https://blog.benj.me/2018/07/04/mozilla-2018-faster-calls-an...

[+] paradite|7 years ago|reply
How does the example in Functions section show that function is first class citizen?

It is merely calling a function within another function. I would expect the second function to take in first function as a parameter (which JavaScript already has).

[+] rkangel|7 years ago|reply
Why do all new languages aimed at the browser seem to be "strongly typed, functional". I have no objection whatsoever, but we've seen Elm, Purescript and now this. Or is my perception skewed by the "HN Echo Chamber"?
[+] always_good|7 years ago|reply
Probably because people already have their pick of those languages on the server but not on the browser client where it's even more important to get things right from a UX standpoint.

Static-typing, immutable datastructures, FP... these are things that even the Javascript ecosystem has been moving towards with React, Typescript, Redux, and more.

If you're making a language for the browser, you can offer a lot of value by rolling these into a single tool. For example, it may be much simpler for you to use Elm than to approximate Elm with your own menagerie of JS tools.

[+] bad_user|7 years ago|reply
"Strong typing" as a term has actually been muddied and doesn't mean much nowadays other than "not C", being re-purposed for marketing reasons. Irregardless of the language, you don't want your language to be weakly typed, although in fact you can say that about most dynamically typed languages.

Leaving the marketing efforts aside, what people want to express is "statically typed". A static type system can prove a lot of things about your program. Traditionally you could rely on (good) static type system to prove that your program is memory safe, or that it has as few runtime errors as possible, preferably zero. N.B. don't fall into the trap of saying that a language like C is statically typed. When you can pass void pointers around and cast that to anything, that's in fact not static typing.

Type theory has a lot to do with math logic actually, the two being highly interchangeable. Statically typed compilers can prove various properties about your program, freeing the developer from writing certain classes of tests, plus it makes refactoring easier.

Refactoring, correctness, these are things people have always struggled with in JavaScript, especially due to JavaScript's nature. Quick, can you tell me the answer to "Math.min() < Math.max()" in JavaScript?

You know it's a trick question, given this is JavaScript, don't you ;-)

---

Other than ClojureScript, I don't know of any other dynamic language targeting JavaScript engines and that's interesting. None. And ClojureScript is only interesting because it is a LISP and because it has sane conventions and defaults.

Languages providing superficial syntactic sugar, such as CoffeeScript, have been a really bad idea and I'm glad that we've moved on.

Therefore the innovation that happens tends to happen in statically typed languages, because there's a lot there left to explore and because such languages tend to bring value.

That said I always wonder what new languages bring to the table versus already available alternatives like PureScript, Scala.js, ClojureScript, Elm, etc. And from the TFA I don't understand what those advantages are.

[+] Latty|7 years ago|reply
I think it's just that JS caters to those features the least, so that's where you see new languages from people that want them.
[+] millstone|7 years ago|reply
What is meant by "zero runtime errors?" An example of a "runtime error" is the head of an empty list; how does Grain handle this?
[+] masklinn|7 years ago|reply
Grain seems way unfinished and the stdlib does not seem to have any such function[0], so here's how Elm handles it instead as it has the same target/ethos of avoiding runtime errors: http://package.elm-lang.org/packages/elm-lang/core/latest/Li...

[0] it's not very useful either if you have pattern matching on lists, you can just match the list directly.

[+] marijn|7 years ago|reply
The DOM example further down the page, which does no result checking on queries that can absolutely fail to find an element, makes me very skeptical of this claim.
[+] frou_dh|7 years ago|reply
Presumably the head function returns a value of type Optional/Maybe, that needs to be pattern matched against statically.
[+] c-smile|7 years ago|reply
IMO modern web language should have some notion of events as in my Sciter ( https://sciter.com/event-handling/ ) for example:

    event click $(table > thead > tr > th) {
      // click on table header cell
      // 'this' is that th element
    }

    class Widget : Element 
    {
       function attached() {...}

       event click $(span.up) { this.increment(); }
       event click $(span.down) { this.decrement(); }

       event mousedown { this.state.focus = true; } 
       ...
    }
[+] joehewitt|7 years ago|reply
YES. Every time I see a new language come out without first class event support it is obvious the creator never wrote a lick of front end code in their life.
[+] mkl|7 years ago|reply
Can you explain how these differ from ordinary callback functions?

I haven't done any web development to speak of, but I've done a fair bit of GUI stuff, doing things like this snippet appears to in languages that don't have "events" as a separate category of thing.

[+] piinbinary|7 years ago|reply
If it's not too late to change, might I suggest doing away with the 'let rec ... and ...' syntax? The way to group mutually recursive bindings can be determined by finding the strongly connected components of the graph of the references between functions, so users don't need to manually specify it.
[+] zumu|7 years ago|reply
I do like the explicitness of marking recursive functions with 'rec', however. What would the syntax look like if it was no longer required?
[+] junke|7 years ago|reply
But then how do you shadow previous bindings?

    let f x = if (x == 0) then 0 else (f x)
[+] alkonaut|7 years ago|reply
How does this compare to Elm and F# (Fable)?
[+] vilu|7 years ago|reply
It seems as if Grain compile to web assembly vs compiling to JS.
[+] zumu|7 years ago|reply
Why not just compile OCaml to wasm? Why another language?
[+] tom_mellior|7 years ago|reply
Poking around in the sources, it looks like this is the OCaml compiler, with the frontend apparently tweaked to accept the new syntax. But this is not mentioned anywhere that I can see. The "Copyright copyright 2017-2018 Philip Blair and Oscar Spencer." line in the README is highly misleading in this context, since most of the actual source files are marked with OCaml's copyright header.
[+] willtim|7 years ago|reply
To have a JavaScript feel with type inference, which I assume they are aiming for given the syntax, I would like to see extensible records (structural typing), which remove the need to declare nominal record types. Purescript and Elm both have this feature.
[+] alexeiz|7 years ago|reply
How does Grain compare with the Reason language from Facebook which is also based on OCaml?
[+] knocte|7 years ago|reply
Doesn't encourage immutability because variables are not readonly by default. I would advice the language designers to require the "mutable" keyword for variables than can be re-assigned. (Similar to F# approach.)
[+] tom_mellior|7 years ago|reply
I think you're mistaken. It seems to me that you need to create an explicit "box" (a reference) to make things mutable.
[+] sbjs|7 years ago|reply
The "DOM Interaction" and "DOM Manipulation" sections just show imperative code. So how is this a fundamental improvement over using jQuery? Especially for a language that markets itself as "seriously functional", it feels like a big step backward. I can't see what benefit this actually brings except being easier for OCaml developers to write web apps with. And even then they'd probably have no trouble learning TypeScript which doesn't need a bridge since it's a strict superset of JS.
[+] bandrami|7 years ago|reply
What's the incentive people have to combine type systems with functional programming? At least in broad strokes I think of typing and immutability (which is the goal of functional-style programming) as solving the same problem in two different ways. If you write control flow that returns values which are immutably bound lexically, what is the point of a type system?
[+] adwn|7 years ago|reply
Immutability gives guarantees at runtime, a type system gives guarantees at compile time. Or, from a different perspective, immutability guarantees a certain property – the value – of an item, while a type system guarantees different properties for all items of a type. Those two concerns are mostly orthogonal to each other.
[+] thangngoc89|7 years ago|reply
Moderators: The title should have the WebAssembly in it to avoid confusion. This is not yet another language that compiles to Javascript.