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.
babel-plugin-macroswas known at the time of original publication as
babel-macrosand 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.
param.macrois 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:
Almost any expression is supported, including nested properties and methods, logical and comparison operators, template literals, etc.
Not your ES5’s partial application
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
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:
_ 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.
See you around!