
Grain: A strongly-typed functional programming language for the modern web - bpierre
https://grain-lang.org/
======
tlrobinson
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/](http://elm-lang.org/)

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

~~~
zachrose
Yeah in general it’s good to avoid sometimes returning a value and sometimes
undefined.

There’s an equivalence you can make between a type and a mathematical set,
where you say that the type is the set of all possible values for that type.

In both JavaScript and math, multiplication of numbers is an operation that
has a special property called closure, which means that the return type is the
same as the input types. Multiply a number by another number and you will
ALWAYS get a number back. The inputs and the outputs are in the same set,
which means you can go nuts multiplying numbers and you’ll never have to check
your values to make sure they’re still numbers; it can be safely assumed.

JavaScript gives NaN a type of “number” as a clever compromise. Instead of
throwing an error when multiplying a number by a non-number, JS allows
multiplication to still return a “number” (`4 * ‘a’ === NaN`) and then you
don’t need to ever worry about accidentally “leaving” the set of numbers (`NaN
* 10 === NaN`).

More likely than not though, multiplying a number and a string is just not a
useful thing to do. With a good type system, a program that could wind up in a
situation of multiplying a string and a number can be rejected by the
compiler.

So in a roundabout answer to your question, a lot of “what then?!” situations
can be entirely avoided with a good type system. For the remaining situations
like accessing an element in an array that may not exist, a Maybe can make a
lot of sense.

(Maybe Float turns out to be just like JavaScript’s numbers, but instead of
NaN you have Nothing.)

~~~
kazagistar
Closure is only good in programming if the answers it gives are always useful.
Otherwise, you run into the same non local error problem that plagues nulls:
the error still happens, but now it happens at some distant usage site, far
away from the cause.

~~~
zachrose
Good point. This is exactly what JavaScript does with NaN and the set of
numbers, and errors are harder to track down because of it.

Computer numbers in general have all sorts of issues like this where clean
mathematical theory is broken. NaN, Infinity and -Infinity, for example, are
all similar to absorbing elements[1] under multiplication in the sense that
once you get that as a result, you can't do the inverse operation (division)
to get back to your original inputs.

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

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

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

Mostly because Go is terrible.

> Haskell's type system is extremely powerful, but it cannot catch this at
> compile time.

The problem is not the type system, it's that Haskell's error handling is
inconsistent.

Elm, despite having a much simpler type system than Haskell, handles this
properly:

    
    
        head : List a -> Maybe a
    

This has nothing to do with the type system itself and everything to do with
how the type system is used (or not used). The designers of Haskell's `head`
decided to make it a partial function, leading to type-checking inputs not
generating any output.

~~~
galaxyLogic
This seems like such an obvious solution it makes me wonder why creators of
Haskell did not think of it? Lists are a very basic type used all the time if
you don't get that right lot of the value of the great type-system is lost I
would think.

~~~
lodi
They did. The elm-style version is in the standard libraries as:

    
    
        listToMaybe :: [a] -> Maybe a
    

([http://hackage.haskell.org/package/base-4.11.1.0/docs/Data-M...](http://hackage.haskell.org/package/base-4.11.1.0/docs/Data-
Maybe.html#v:listToMaybe))

~~~
galaxyLogic
That's a lengthy name for the simple operation of getting the first element of
a list if there is one. I would have preferred 'car' :-)

~~~
lodi
It's a bit verbose, but this kind of thing is far less prevalent in Haskell
anyway; most of the time you destructure a list through pattern matching, not
explicit head/tail functions.

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

~~~
jholman
What makes you think that the HN poster has any affiliation with the project,
or vice versa?

~~~
tyrel
Yeah, knowing one of the developers personally, they actually didn't post
this. Someone else posted this to Twitter/Reddit/HN.

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

~~~
ealhad
Moreover, they don't really _show_ anything to make their point — I can't see
any type annotations on the frontpage.

~~~
HindleyMilnerTI
> it's 2018 > type annotations

[https://en.wikipedia.org/wiki/Type_inference](https://en.wikipedia.org/wiki/Type_inference)

~~~
leshow
Type annotations can be helpful in writing maximally polymorphic code and in
self documentation, independent of type inference. When I write Haskell, I
still annotate my code.

~~~
always_good
Also, throwing out all type annotations makes code pretty hard to read,
especially outside your IDE like in a Github repo.

Taken to an extreme, it feels like reading a dynamically-typed language where
you have to keep the various variable types in your head when debugging.

There's a middle ground.

------
truncate
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](http://home.pipeline.com/~hbaker1/CheneyMTA.html)

~~~
munificent
It appears to allocate memory but never free it:

[https://github.com/grain-
lang/grain/blob/master/runtime/src/...](https://github.com/grain-
lang/grain/blob/master/runtime/src/core/heap.js)

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
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/](https://reasonml.github.io/)

~~~
wffurr
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...](https://blog.benj.me/2018/07/04/mozilla-2018-faster-calls-and-anyref/)

~~~
nine_k
Isn't it still pretty experimental?

~~~
vanderZwan
Well, Grain is as well so is that really an issue?

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

~~~
Tehnix
I was hoping for currying, but was disappointed when all the functions were
fully applied. It don’t really see either, how this tells anything about
first-class functions :/

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

------
millstone
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
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...](http://package.elm-
lang.org/packages/elm-lang/core/latest/List#head)

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

------
c-smile
IMO modern web language should have some notion of events as in my Sciter (
[https://sciter.com/event-handling/](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; } 
           ...
        }

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

~~~
c-smile
This

    
    
        observable.on("click", observerFunc);
        // or observable.addEventListener(...);
    

is an executable statement. And this

    
    
        class Widget : Element {
    
          event click { … observer's code … }
        }
    

is a _declaration_.

That executable statement needs to be called at some point of time. So the
observable can be in two states - with and without that event handler.

While class _declaration_ is an invariant (at least in Sciter) - as soon as
element is in DOM it has that class and consistent set of event handlers in
place. Such binding is done by, again, CSS _declaration_ :

    
    
        widget {
          prototype: Widget url(path); // <widget> controller
          display: block;   
          ...  
        }  
    

"I've done a fair bit of GUI stuff"

AFAIR VB6 and Delphi allow to declare event handlers without explicit/runtime
binding - that's close as a concept to the above.

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

~~~
junke
But then how do you shadow previous bindings?

    
    
        let f x = if (x == 0) then 0 else (f x)

~~~
piinbinary
Potentially controversial opinion: shadowing bindings is a bad idea.

------
alkonaut
How does this compare to Elm and F# (Fable)?

~~~
vilu
It seems as if Grain compile to web assembly vs compiling to JS.

~~~
masklinn
Nothing requires Elm or Fable to compile to js. I don't know about Fable but
both Elm's maintainer and its community are interested in eventually targeting
wasm.

------
zumu
Why not just compile OCaml to wasm? Why another language?

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

~~~
afrisch
Many files are taken from the OCaml compiler and then adapted, but changes
seems a bit deeper than just a different syntax. It would indeed seem fair for
the authors to at least make it clear in the toplevel README that the front-
end (parsetree representation and type-checking) is indeed / started as a fork
of the OCaml code base.

Considering ongoing efforts to create a WebAssembly backend for OCaml (and
thus Reason), I wonder what would be the selling point of Grain.

~~~
ubertaco
Even syntactically, this looks a lot like if you took ReasonML, _disallowed_
type annotations, did away with its ecosystem, and compiled to WebAssembly.

Aside from the wasm compilation, I don't see why this exists.

~~~
senorsmile
That was my question only having recently started looking into ReasonML
seriously. Sounds like I'll just stick to Reason!

------
willtim
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
How does Grain compare with the Reason language from Facebook which is also
based on OCaml?

------
knocte
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
I think you're mistaken. It seems to me that you need to create an explicit
"box" (a reference) to make things mutable.

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

------
LolNoGenerics
Is the language incomplete like the docs?

------
bandrami
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
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
Moderators: The title should have the WebAssembly in it to avoid confusion.
This is not yet another language that compiles to Javascript.

------
slifin
This looks nice, but it does need more documentation and an online
playground/eco system which understandably should come in time

------
spinningslate
I'd hope this is indicative of wasm precipitating a wave of creativity. From a
purely technical point of view I actually don't care. Elm is fast, complete,
elegant and has perhaps the most user-friendly compiler on the planet. The
fact it happens to target js and not wasm (at this point) is practically
irrelevant.

I can see, though, that wasm might encourage experimentation by a wider group
of people who are put off by targetting js.

Coming back to Elm, it'd be great to see others consider the wider
architectural questions. The Elm architecture and language are beautifully
complementary. That's stark when looking at react - which was copied from (or
at least inspired by) the Elm architecture. However, being js-based, it
doesn't have the mutual consistency of elm - and so has to resort to syntactic
gymnastics.

Anyway, that's getting off topic. Grain is clearly early stage. Anyone
motivated enough to conceive and deliver a language deserves some
encouragement. Will be interesting to see how it evolves.

------
tylerhou
How does Grain prevent bottom with static type checking? Totality is
undecidable in Turing complete languages, so “no runtime errors” is probably
false (maybe they “solved” the halting problem?).

------
yosefzeev
Arch installs some version of dune-project the make command does not
recognize. Not in the mood to edit a package build just to try this
programming language out.

------
devxpy
Dart anybody?

~~~
cmmartin
Grain is quite different. Grain is more functional (no classes or context,
tuples). Dart compiles to Javascript. Grain compiles to web assembly. Dart
requires you to define types. Grain provides type safety and zero runtime
errors without ever defining types manually.

~~~
pmontra
The documentation about types says to look at the Readme of the compiler,
which is very short and doesn't tell anything about types. Same thing for many
other entries in the side menu. The examples don't have any type declaration.
So, type inference and no reuse of the same variable with a different type,
even when forcing mutation? Unfortunately the documentation is still too
skinny.

A note to language designers: I can understand the { } but if you use them why
do you also need the ( ) around the conditionals here?

    
    
      if (n <= 1) {
        n == 0
      } else { 
        isOdd(n - 1)
      }
    

The parser can find where the condition starts (after the if) and ends (at the
{) and we won't have to type two useless characters. It's ergonomics.

~~~
andyonthewings
( ) are only optional if { } are mandatory, which is not true for some
languages. e.g. in Haxe, one can write

    
    
        static function isEven(n)
            return if (n <= 1)
                n == 0;
            else
                isOdd(n - 1);
    
        static function isOdd(n)
            return if (n <= 1)
                n == 1;
            else
                isEven(n - 1);
    

i.e. An if expression consists of other expressions that may or may not be a
block expression ({ }).

~~~
pmontra
There is a great deal of variability in languages about that.

Python ends if lines with a : which might help the parser, but maybe not
because it was a late addition to help readability. For me it adds work when
moving code around because I have to add or remove the :

Ruby does totally without any terminator in that context (in other contexts it
has its own {} or do...end). The parser keeps processing successive lines
until it understands that what follows can't belong to the conditional. I
prefer that approach because it's the language/compiler that has to help me,
not the other way around.

------
yetkin
This is just a prototypal IDEA right? motivation? benchmark? demo? build
system?

------
tom_mellior
Has anyone found in the sources if/how they treat garbage collection?

~~~
vilu
No, I would be interested in that too. I don't know anything about Web
Assembly but maybe GC is handled by Web Assembly?

~~~
masklinn
There are plans to eventually integrate GC into wasm, but at the moment the GC
has to be implemented and bundled in the wasm file. That is in fact one of
Rust's advantages there. And of course wasm supporting a GC does not mean
languages targeting wasm will want to use it (and lose the flexibility of
providing their own).

------
kowdermeister
A functional language with readable syntax for mere mortals?

Sign me up :)

------
junke
isOdd and isEven don't work with negative numbers.

------
crimsonalucard
anybody try it?

