Partial Application & Lambda Parameter Syntax for JavaScript

I present param.macro — a plugin for Babel that adds lambda parameters & partial function application to JavaScript at compile time. You can give it a try right now, in your browser, with its online playground. In this post I’ll introduce you to what it’s all about.

Oh the things we’ll accomplish together

The shape of things

A not-so-long time ago in a place not necessarily all that far away, Kent C. Dodds created babel-plugin-macros out of thin air and a little inspiration. The project is basically an abstraction of Babel plugins that allows for sharing zero-configuration macros — pieces of code that change their surroundings at compile time.

Note that babel-plugin-macros was known at the time of original publication as babel-macros and has since been renamed. This story’s been updated to reflect that.

It turns out that babel-plugin-macros is a solid fit for my plugin's intentions: explicit syntactic partial application and lambda parameters. It shares the idea that things should be clear and explicit while at the same time reducing boilerplate. It's even easy to set up: all you have to do is install it and add it to your Babel plugins configuration. From then on, any macro package like param.macro is just an import away in each file.

While param.macro is meant first and foremost to be used with babel-plugin-macros, it can also be used as a standalone Babel plugin. See the standalone plugin section of the readme for usage.

Expressions as functions, functions as expressions

Languages like Scala and Kotlin, which were the primary inspirations for this project, have a concept of lambda parameters. In Kotlin specifically, this is a shorthand for anonymous functions where, if you don’t specify an argument, one will automatically exist with the name it:

It just so happens that this example maps exactly to how we’ll be able to do it in JavaScript using our macro plugin:

Almost any expression is supported, including nested properties and methods, logical and comparison operators, template literals, etc.

Not your ES5’s partial application

You’re probably familiar with JavaScript’s Function.prototype.bind method, which means you've already encountered partial application. You might also be aware of the many libraries that provide partial application through a helper function, like Lodash or spots.

The problem is that bind has limits, since you can only bind arguments in order and there's no concept of 'placeholders'. And libraries — some of which do support placeholders — are runtime constructs that can hinder performance in not-so-insignificant ways. To avoid these issues, you could just use anonymous functions and handle the arguments however you want. But we want to reduce boilerplate, not introduce more.

What if you wanted z to be 2, and not x? Well that rules out using bind, but you could still use Lodash or an anonymous function:

But we can make it so, so much sweeter. Time for param.macro to shine:

Behind the scenes, this compiles down to plain old anonymous functions like you’d write yourself. This means no excessive runtime overhead and no production dependency, since the param.macro import is removed during compilation.

And, not to be outdone by the lambda parameter macro, placeholders also support arbitrary expressions, including but not limited to nested properties and methods.

But then isn’t it === _?

Right about now is the time I should point out two very important differences between the two symbols provided by the plugin, and it all comes down to what they output. The reason there are two separate symbols in the first place is to have clear semantics in all cases.

The first difference is in scoping:

While these look like they might be the same, they’ll come out acting very different:

The _ partial application symbol will always wrap its nearest parent function call, while the it implicit parameter will always be transformed in place.

The second major difference is that it will always refer to the first argument of the generated function, whereas each _ will refer to the next argument of the generated function.

Cause and effect

param.macro offers several advantages, both on its own and when compared against alternatives like libraries.

Usage is explicit

In order to use the macros, they must first be imported. This means every time they're used, the symbol is defined in scope and anyone reading the code will be able to follow the breadcrumbs.

Encourages good code practices

Partial application promotes the creation and use of composable, reusable functions. These macros are an attempt at making that practice as elegant as possible.

Performance is maintained

By reducing runtime dependencies compared to a library, you shrink the size of your deployment as well as avoid the potential performance hits you can incur with runtime alternatives. Libraries providing partial application have to check at each call what kind of arguments they’ve been provided and if any of them are placeholders, which can become costly.

Introduces you to a growing ecosystem of macros

babel-plugin-macros has really just begun. Along with param.macro, there are many opportunities for plugins using it to provide compile time optimizations. Once it's set up, all you have to do to use another macro is install and import; there's no per-plugin configuration necessary (though it's possible).

The house that we built

I hope this has given you some ideas of what param.macro could do for you, but please feel free to visit the online playground where you can test it for yourself immediately in your browser. You can also visit the repository on GitHub to see the code for yourself. It's worth noting that the plugin is bootstrapped - it uses itself in its source!

Please keep in mind that contributions of any kind are always welcome, and be sure to open an issue if you come across anything ungainly, unsightly, or unbecoming. I also exist on Twitter.

And I can’t miss this opportunity to thank all the people who do phenomenal work on Babel and Kent C. Dodds for his work on babel-plugin-macros.

See you around!