Photo by Max Böttinger on Unsplash
Getting started with Grain by examples
Part 1: the beginning
Table of contents
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 aNone
Option.unwrap(option)
: get the value of aSome
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 aResult<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).