I find one of the features that contributes most to comprehensible code when it is available is “Pipelining”. Pipelining has many different meanings in the technology domain, but what I am specifically referring to is having the ability write code that allows the output of one function to be used simply as the input of another, without needing to add in any intermediate variables.

Example

Take a simple string manipulation where we want to get someone’s initials from a full string. We could do this with the following code in gleam

import gleam/string
import gleam/list
import gleam/result

pub fn main() {
  let words = string.split("Rups Ruts", " ")
  let firsts = list.try_map(words, string.first)
  let unwrapped = result.unwrap(firsts, ["Nope"])
  let joined = string.join(unwrapped, " ")
  let result = string.append(joined, "!")
  echo result
}

Output: "R R!"

This code is fine, but needing to pass through all the intermediate values like this is rather tedious when it is quite obvious what the manipulation is. It can also be a bit error prone if variables are shadowed and allows the opportunity to pass in an old, incorrect argument. Instead of this, we can use Gleam’s Pipeline syntax:

import gleam/string
import gleam/list
import gleam/result

pub fn main() {
  "Rups Ruts"
  |> string.split(" ")
  |> list.try_map(string.first)
  |> result.unwrap(["Nope"])
  |> string.join(" ")
  |> string.append("!")
  |> echo
}

Output: R R!. Here we see the |> (pipeline) operator being used to feed the output from one function as the first argument of the following function. We also see another neat feature of gleam where, if the first argument of a function is omitted, we automatically generate a unary function that can be used in a pipeline. There is even syntactic sugar for converting n-ary functions to unary functions with the underscore operator to make creating these pipeline-able functions even easier:

pub fn main() {
  echo takeaway(2, 1)
  1
  |> takeaway(2, _)
  |> echo
}

fn takeaway(a: Int, b: Int) {
  a - b
}

Output:

1
1

Availability

Pipelines are certainly available in Gleam. I feel like pipelines and use syntax for monadic operations are really the core of the language. You can, however, find a very similar feel from what we used to call Fluent Interfaces in OO languages to get a very similar feel (though this is sadly less flexible than what is supported by gleam). It is certainly the way that I tend to write rust error handling code using the Result Helpers map and and_then. The same code as above looks like the following in rust, though I did need to add the dependency of Itertools to avoid an intermediate collect into a Vec of Strings.

use itertools::Itertools;

fn main() {
  let initials: String = "Rup Ruts"
    .split(" ")
    .filter_map(|word| word.chars().next())
    .join(" ");

  println!("{initials}!");
}

Caution

As with anything, one can go too far with this and make code that is quite incomprehensible… Particularly if you need to use a pipeline within one of the functions in your pipeline… You may be hurting your readability.

Examples in the wild!