Pipelining

Pipelining
Super Mario Bros. world 1-2 warp level pipes.

After some reflection I realized my batch was lacking in two departments: Working with folx on stuff that "matters".

I decided to significantly up my tracking down of people to learn from and pair with – by initially offering my own services, expert or lay. I tried to use my editing experience to help someone craft an outline for their presentation, and another person work on UI/UX for their website. This is great! I plan to continue to do more of this, reaching out to people and really trying to apply myself.

Insofar as finding stuff that matters to work on, I first want to assure myself that everything matters. Even dumb little go-nowhere projects are important. But for my time here, I want to bias to projects that will benefit greatly from others oversight, input, and experience. My DoTryCatch project is a perfect example. I could benefit so much from others' input on a web app (a domain where my unsophisticated understanding is wont to wander).

As such, I've been writing a lot more Swift code than Gleam. But one thing I did to help Gleam up my understanding was introduce a new operator into Swift – the pipeline operator.

In Gleam, this operator |> acts as a forward applicator, taking the output of the previous operation and supplying it as an argument for the next operation. The following example, taken from the Gleam language tour, demonstrates a few multipart, relatively uncomplex pipes.

import gleam/io
import gleam/string

pub fn main() {
  // Without the pipe operator
  io.println(string.drop_start(string.drop_end("Hello, Joe!", 1), 7))

  // With the pipe operator
  "Hello, Mike!"
  |> string.drop_end(1)
  |> string.drop_start(7)
  |> io.println

  // Changing order with function capturing
  "1"
  |> string.append("2")
  |> string.append("3", _)
  |> io.println
}

Writing an operator like this for Swift is relatively straightforward, and the PointFree guys did it in their first episode.

infix operator |>: ForwardApplication

func |> <A, B>(a: A, f: (A) -> B) -> B {
  return f(a)
}

precedencegroup ForwardApplication {
  associativity: left
}

What does this do? Briefly:

Declares an infix operator (think of the word prefix – prefix is a piece of language[^1] that goes pre (or before) a word, an infix is a piece of language that goes within a word. English doesn't have many infix (or, technically, interfix) morphemes, but you can think of something like "un-fucking-believable" as having a "fucking" infix.

The function declaration describes the behavior of |>. It takes two arguments, an a of type A and an f of type (A) -> B, or a mapping from A to B. The function returns a B. Then the function applies the mapping to the provided A. In other words, it simply takes its first argument and applies the second argument to it.

Thinking of the mathematical operation add as a function, in C-style programming we would call this as add(Int, Int) -> Int. But the infix version would use the + operator and be the familiar version 2 + 3.

Functions are first-class citizens in Swift, so when we say (A) -> B, we are talking about a function.

var function: (String) -> Int = { $0.count } // a function mapping a String to an Integer
let a = "A man, a plan, a canal: Panama." // a good ol' fashioned String
function(a) // 31

So if we use our new operator as an infix, we could call:

a |> function // 31

Gleam does this everywhere, and, at least for me, does it in a way that makes it confusing sometimes to work with, because the elegant piped solution is just out of grasp.

But by adding this to Swift, I've given myself more practice with the structure in my own work, and I can break down complex functions into series of pure functions I can compose together. For instance:

let g: (String) -> String = { $0.uppercased() }
let f: (String) -> String = { "The sentence is: \($0)" }

a |> g
  |> f // "The sentence is: A MAN, A PLAN, A CANAL: PANAMA."

Cool! g and f are easy enough to test in isolation, and we can pipe one's output to the other's input. This is good for taking some type and applying a number of different operations to it one after another.

The final piece of the declaration is the precedencegroup, which is a Watership Down-level rabbit hole. The curious among you, go here. Otherwise, you can just think of this as the mechanism that enforces PEMDAS across function application.