Skip to content

Instantly share code, notes, and snippets.

@haltcase

haltcase/blog.md Secret

Created November 29, 2017 02:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save haltcase/8a6ebb9fbfe213c2a21de072cc6cea03 to your computer and use it in GitHub Desktop.
Save haltcase/8a6ebb9fbfe213c2a21de072cc6cea03 to your computer and use it in GitHub Desktop.

Partial application and lambda parameters using babel-macros

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

The shape of things

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.

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:

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

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.

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

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:

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.

Cause and effect

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

  1. 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.

  2. 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.

  3. 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.

  4. 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).

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 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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment