Getting started with Grain by examples

Part 1: the beginning

Featured on Hashnode

I was always fascinated by functional programming, but I'm not a (good) "functional programmer". My first steps with functional programming were with Scala, which was rough. I gave up quickly the first time and returned to my first love: JavaScript 🥰. Some years ago, I decided to give another try to functional programming thanks to this ebook Mostly adequate guide to FP (in javascript). And finally, in the end, I understood some concepts like Functor, Monad, Result, Option, etc. Then, I played with this beautiful JavaScript library: Monet.js. I felt more confident with Scala ... before discovering Kotlin 😍. But, as I said before, my primary programming language is JavaScript.

When I started to take a strong interest in WebAssembly, during my research, I discovered the Grain language (GrainLang), which is a functional language that only targets wasm in terms of compilation. It's an exciting quality, but I think the essential quality of Grain is its human readableness. So, It is time to get back to functional programming.

But let's see Grain in action.

In this blog post, I will provide simple examples that instantly get you started with Grain.

We always need a "Hello World"

Grain is straightforward to install. Have a look at the documentation: Getting Grain or use my "ready to use Grain Gitpod sandbox". You can open it directly with Gitpod: gitpod.io/#https://github.com/wasm-universi...

"Hello World" is the best way to check your setup. Create a file named hello-world.gr with the below source code:

/* Hello world with Grain */

print("👋 Hello World 🌍")

Then:

  • Compile it: Grain compile hello-world.gr (it will generate a new file: grain run hello-world.gr.wasm)
  • Run it: grain run hello-world.gr.wasm

You should get a fabulous and lovely message: "👋 Hello World 🌍"

Remark: If you installed wasmtime, you could execute the wasm file like this: wasmtime hello-world.gr.wasm

How to install WasmTime: curl https://wasmtime.dev/install.sh -sSf | bash btw: wasmtime is already installed in my Gitpod sandbox.

Variables, Tuples, Records

Defining and printing a variable is simple:

let firstName = "Bob"
let lastName = "Morane"

print("👋 Hello" ++ firstName ++ " " ++ lastName)

you'll get: 👋 HelloBob Morane

By default, Grain variables are immutable. But you can use the mut keyword to be able to reassign a variable, as I do, for example, below with a Tuple:

let firstName = "Bob"
let lastName = "Morane"

// Human tuple
let mut human = (firstName, lastName, 42)
print(human)
human = (firstName, lastName, 53)
print(human)

you'll get: ("Bob", "Morane", 42) then ("Bob", "Morane", 53)

You can also use Record type (kind of structure) to manage your data:

// you have to define the type of each field
record Human { mut firstName: String, lastName: String, age: Number }

let bob = { firstName: "Bob", lastName: "Morane", age: 42 }
print(bob)
bob.firstName = "Bobby"
print(bob)

you'll get:

{
  firstName: "Bob",
  lastName: "Morane",
  age: 42
}
{
  firstName: "Bobby",
  lastName: "Morane",
  age: 42
}

Of course, we need functions

Functions in Grain are like closures (functions are first-class citizen). Here, I want to write a function that takes a List<String> as a parameter, and return a String:

import List from "list"

let sayHello = (words) => {
  let newList = List.append(["👋", "Hello"], words)
  List.join(" ", newList)
}

print(sayHello(["Bob", "Morane"]))
  • you'll get: 👋 Hello Bob Morane
  • you don't need the return keyword

Handling errors

Functional programming allows you to handle errors elegantly, and that's undoubtedly my favourite thing about this type of programming.

null and nil do not exist 🎉

Grain brings us the Option type. An Option can be either a Some or a None, and then null nor nil do not exist. Let's write a function that returns a None if its parameter is 0 and a Some if its parameter is different from 0:

import Option from "option"

let howManyThings = (numberOfThings) => {
  if(numberOfThings==0) {
    None
  } else {
    Some(numberOfThings)
  }
}

let thingsNb = howManyThings(0)

if(Option.isNone(thingsNb)) {
  print("😢 you gave me nothing")
} else {
  print("🥰 thank you, you gave me something:")
  print(Option.unwrap(thingsNb))
}
  • you'll get: 😢 you gave me nothing
  • Option.isNone(option): test if the result of the function is a None
  • Option.unwrap(option): get the value of a Some

Pattern matching

To test the return values of your function, you can use Pattern matching instead of if else:

match (howManyThings(42)) {
    Some(numberOfThings) => {
      print("🥰 thank you, you gave me something:")
      print(numberOfThings)
    },
    None => print("😢 you gave me nothing")
}

you'll get:

🥰 thank you, you gave me something:
42

Currently, exceptions cannot be caught 😢

Currently, exceptions cannot be caught and will crash your program. Most of the time, it's not a big issue because Grain returns a type that can be tested. But not every time. Let's try to do some divisions:

import Number from "number"

print(Number.div(42.5,0))

If you compile and run the program, you'll get Infinity. That means you can test it:

let result = Number.div(42.5,0)
if(Number.isInfinite(result)) {
  print("😡 Division by zero")
}

But if I use an Integer instead of a Float: Number.div(42,0), the program will raise an exception (DivisionByZero: Division by zero) and then crash.

I found a workaround 🤓

For example, you can use Float64

import Float64 from "float64"

let a = 42.0d
let b = 0.0d

print(Float64.div(a,b))

you'll get Infinity

So, you can test the result, but you need to convert it to Number before with the Float64.toNumber helper:

let a = 42.0d
let b = 0.0d

let result = Float64.div(a,b)

if(Number.isInfinite(Float64.toNumber(result))) {
  print("😡 Division by zero")
}

🤔 fine, but I would like to write something fancier.

Let's write a division function that works with Numbers and that returns a Result

Result is my favourite type (I want a Result type in every language 🙏). A Result can be either an Ok or an Err. My division function will look like this:

import Result from "result"
import Number from "number"
import Float64 from "float64"

let division = (x, y) => {
  let a = Float64.fromNumber(x)
  let b = Float64.fromNumber(y)
  let r = Float64.toNumber(Float64.div(a,b))

  match(r) {
    r when Number.isInfinite(r) => Err("😡 Division by zero"),
    _ => Ok(r)
  }
}

division returns a Result<Number, String>

And, now you can test the division function result like that:

match(division(5, 0)) {
  Err(message) => print(message),
  Ok(value) => print(value)
}

match(division(5.5, 0)) {
  Err(message) => print(message),
  Ok(value) => print(value)
}

match(division(84, 2)) {
  Err(message) => print(message),
  Ok(value) => print(value)
}

You'll get:

😡 Division by zero
😡 Division by zero
42.0

👋 That's it; this little tour of GrainLang is over. I hope this made you want to dig deeper. I will, of course, write a sequel very soon. Stay tuned for the next episode (We will try to do more complicated things).