Skip to content

Adding "hack" support to F# style with the "(eiffel) tower" operator "|>$=>" #191

@theScottyJam

Description

@theScottyJam

@tabatkins has put together this wonderful essay that weighs the differences between F# and hack style pipes, and ultimately concludes that the differences are minor, but hack-style is a little better, in part due to the fact that for most use cases, hack-style will be more concise.

I've got to agree with @tabatkins on all of this, however, as I mentioned in a comment over there (that I decided to bring up over here), there's a minor thing we can add to F#-style pipes, to have it reap almost all the same benefits as hack-style (all but the nice support hack-style has for await, but I'm ok having a more verbose solution for that).

But first - some background.

What we're aiming for is to find a simple, yet easy-to-read version of the pipeline operator. With that in mind, I want to make a comparison between two fictional pipeline operator syntaxes - |next> and % @.

id |next> ? + 1 |next> groupsFromUser(?, true) |next> rolesFromGroups(?)

id % @ ? + 1 % @ groupsFromUser(?, true) % @ rolesFromGroups(?)

What I want to point out from the above example, is how much more readable the |next> version is. Why? Because it's a single unit. % @ is two separate things, which adds a lot of visual clutter and makes it harder to glance at the expression and see what's going on. This is the issue that F# style pipes currently has: more often than not, an F# pipe requires a function literal afterwards, causing you to write three distinct "units" in order to do anything useful - the "|>", an identifier, and "=>":

id |> x => x + 1 |> u => groupsFromUser(u, true) |> g => rolesFromGroups(g, false)

Gross 🤮️

So what's the one thing we can add to F#-style pipes in order to save it? We can popularize the new "(Eiffel) tower" pseudo operator |>$=>, which combines those three units into one, and effectively brings the benefits of hack-style pipelining into F#-style. Notice that |>$=> is nothing more than the pipeline operator, an identifier, and "=>" smashed together into a single unit, but it greatly improves the readability of code. Here's the same example, using the tower operator:

id |>$=> $ + 1 |>$=> groupsFromUser($, true) |>$=> rolesFromGroups($, false)

You may argue that |>$=> is such a loooong operator, but remember that our goal isn't to reduce character count, it's to improve readability. If we wanted to write more concise code, we would just do what we can already do today - remove the pipeline operator and call the functions in a normal fashion. That will always be more concise than any pipeline operator proposal we come up with.

Here's some more examples, adapted from the README:

let newScore = person.score
  |> double
  |>$=> add(7, $)
  |>$=> boundScore(0, 100, $);

getAllPlayers()
  |>$=> $.filter( p => p.score > 100 )
  |>$=> $.sort()
  |> Lazy
  |>$=> $.map( p => p.name )
  |>$=> $.take(5)
  |>$=> renderLeaderboard('#my-div', $);

I love the power of the hack-style operator, but I also love the simplicity of F#-style (we're not having to add a special topic concept that's unique to a single operator). I know it's not conventional to do this kind of thing, but I advocate that we run with F#-style and attempt to popularize this tower operator with it (we can include it in the proposal's README, eventually put a page about it on MDN, etc), thus giving us the best of both worlds.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions