Eccentric Developments


Raybench - Gleam

Since Gleam reached version 1.0 everyone is talking about it, so I couldn't resist the hype and had to try it myself, writing the customary raybench version of it.

Gleam is a functional programming language that runs on top of the Erlang runtime, similar to Elixir. And while its main focus is on building web applications, I am using it to build a simple path tracers.

Instalation is very simple if you use Homebrew, just run brew install gleam and you are good to go. Other installation methods are mentioned here, but you will probably have to manually configure the dependencies tho.

In the same vein, creating a new Gleam project is as simple as running gleam new <project name>, and just as soon as that finishes, you can run it with the command gleam run.

While I only spent a small amount of time writting this raybench implementation, I still found some things I liked and disliked about Gleam:

1. Good: The pipeline operator |>.

This is something I whish more programming languages had. The pipeline operator lets you write function sequences that feed on the resoult of the previous one in a more natural way.

For example, instead fo writing a chain of functions like this:

let res = h(g(f(10)))

You use the pipeline operator to more easly show the flow of data and sequence of operations:

let res = 10 |> f |> g |> h

2. Good: Strict type system.

Algebraic data types, strict type checking, update syntax. It has it all!

3. Good: Pattern matching.

Robust pattern matching that supports guards. You can even pattern match inside lists and capture the element groups.

case closest_hit, hit {
    #(False, _, _), #(True, _, _) -> hit
    #(True, _, _), #(True, _, _) if closest_hit.1 >. hit.1 -> hit
    _, _ -> closest_hit
}

4. Good: No Nulls!

This will always be a win in my book. Options > Nulls any day of the year.

5. Weird: Using { } as code block delimiters and parenthesis.

In hindsight it makes sense, since blocks return the value of the last instruction executed, but it took me offguard when trying manipulate the order of some operations.

6. Not great: It is hard to find the libraries.

This is most likely an issue with me not being familiar with the Erlang ecosystem, but finding a library is complicated, even using hexdocs.

The biggest hurdle I faced was trying to run the multi-core actor example in the home page. If you just copy/pase the code into an editor, it will not run.

The problem is that the library for tasks is not specified, and the language tour, nor the documentation tells you which import you need. It is gleam/otp/task, if you are curious.

Fixed Versionj

import gleam/int
import gleam/io
import gleam/list
import gleam/otp/task

fn spawn_task(i) {
  task.async(fn() {
    let n = int.to_string(i)
    io.println("Hello from " <> n)
  })
}

pub fn main() {
  // Run loads of threads, no problem
  list.range(0, 200_000)
  |> list.map(spawn_task)
  |> list.each(task.await_forever)
}

And you also need to add the gleam_otp = ">= 0.10.0" dependency to your gleam.toml file.

Conclusion

Overall the Gleam programming language is very nice, it offers a simple syntax and, when things compile, you can be sure that the application will run as expected, unless you have an error in the business logic, but the compiler can hardly help you there.

The available tooling works very well,, at least in the experience I had using the VSCode extension and the code compilation is very fast. Too bad I can't say the same about its number crunching performance.

In this raybench test, Gleam took 107mins while Rusts spent 14sec on the same task.

Enrique CR - 2024-05-27