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.
https://gist.github.com/1baffdd2187180c24ff4194c819a8db0
A not so long, long time ago in a place very near here, Kent C. Dodds created babel-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.
It turns out that babel-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.
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:
https://gist.github.com/de05c3f47a347c1b64cb7a7c548f6b7c
It just so happens that this example maps exactly to how we'll be able to do it in JavaScript using our macro plugin:
https://gist.github.com/b7bb12d4abe0fab16dd689419946ef97
Almost any expression is supported, including nested properties and methods, logical and comparison operators, template literals, etc.
https://gist.github.com/a3e273bed6eb3b26932194e01a72305b
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.
https://gist.github.com/b499a4dea44a49bdb27770a146caa280
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:
https://gist.github.com/e356cb514280be3bba28ef9640e8c241
But we can make it so, so much sweeter. Time for param.macro
to shine:
https://gist.github.com/ae60b5f85bb1a67238b65c5d6e9ba107
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 expressions, including but not limited to nested properties and methods.
https://gist.github.com/47848b56f2ecffe53b221dc0f9b2ea85
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:
https://gist.github.com/d8746c5236e4dbcc046a2c8db48b9df5
While these look like they might be the same, they'll come out acting very different:
https://gist.github.com/9e53ed61a1880862e5faa8dedb115b93
The _
partial application symbol will always wrap its nearest 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.
partial.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
import
ed. 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-macros
has really just begun - and there are many opportunities for 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).
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 youself. It's worth noting that the plugin is bootstrapped - it uses itself!
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-macros.
See you around!