The argument transforms the syntax entirely. What was pristine and simple becomes noisy and complex.

Why can't we write this?

arr.map(:join, '-')

I assumed for a long time this was a limitation of Ruby, but then I got curious about that &, which I only half understood.

Turns out the & is needed any time a literal block is expected and you pass an object that responds to to_proc instead.

Despite appearances, then, both versions of map with join take a block and zero arguments. In the map(&:join) version, to_proc is called on the symbol :join, and the result is passed to map. Symbol defines to_proc in the way you'd expect:

Refining the Loose Thread

With refinements, we can support the arr.map(:join, '-') syntax we want, do it exclusively in code that enables the feature, maintain full backward compatibility with the default syntax, and make the & optional.

These features, and everything else discussed in this post, are available in the pretty_ruby gem:

Continuing to Tug...

We've stumbled upon a new way to represent Procs, but what if we need to chain them together? For example, say we need to increment and uppercase every letter in a list, and then join them with hyphens:

Once again, the brackets and our variable x are syntactic boilerplate. Taking inspiration from the Unix pipe | and Elixir's pipe operator |>, we can refine Ruby's unused >> method on the Array and Symbol classes and write:

But notice how finnicky this is - two dots and two negative indexes for take, three dots and one negative index for drop - and observe how "dropping" is implemented by "taking the complement." The solution doesn't express its intent.

And then there is the larger dissonance between the ad-hoc negative solutions and the clearly named take and drop methods.

Let's fix both problems and complete our set of four symmetric methods:

Completing first and last

Built into Array are shortcuts for the special cases take(1) and take(-1), more commonly known as first and last. While rails offers cute shortcuts for second, third, fourth, and fifth, neither it nor Ruby provide the more substantial ones for drop(1) and drop(-1), familiar to functional programmers as tail and init.

first/ last and tail/ init make mirror images, and the pairs themselves make "complement images" of each other. Reflecting about these two different symmetry lines, you can start with any one of the methods and generate the other three:

Tying the Threads Together

While cleaner syntax and declarative code are always valuable, and hacking Ruby is plain fun, I think there's more to refinements like these.

They change your mindset. They shift your perspective from passive consumer to creator. You see the language as something living, shaped by forces and choices and even mistakes, rather than something handed down from on high.

Most of all, changing the language forces you to understand it more deeply.

If you like these changes, you can install the pretty_ruby gem and start using them yourself.