SkillAgentSearch skills...

Param.macro

Partial application syntax and lambda parameters for JavaScript, inspired by Scala's `_` & Kotlin's `it`

Install / Use

/learn @haltcase/Param.macro

README

param.macro · Version License JavaScript Standard Style

Partial application syntax and lambda parameters for JavaScript, inspired by Scala's _ & Kotlin's it. Read more about this macro in the intro blog post ["Partial Application & Lambda Parameter Syntax for JavaScript"][blog].

try it live on the online playground


overview

param.macro provides two main symbols — it and _.

it can be used in an expression passed to a function which implicitly creates a lambda function in place accepting a single argument.

The _ symbol is inspired by Scala and is used as a placeholder to signal that a function call is partially applied — the original code isn't actually called yet, but will return a new function receiving the arguments you signified as placeholders. Think of the values that aren't placeholders as being "bound", and you'll provide the rest later.

Check out the examples section and the [official introduction post][blog] if you'd like to see how these can be useful.

installation

yarn add --dev param.macro

Make sure you also have [Babel][babel] and [babel-plugin-macros][babel-plugin-macros] installed (the following use Babel v7, see usage for more):

yarn add --dev @babel/cli @babel/core babel-plugin-macros

... and configured with Babel:

module.exports = {
  presets: [],
  plugins: ['babel-plugin-macros']
}

for usage without babel-plugin-macros, see standalone plugin

Then just import and use:

import { _, it } from 'param.macro'

it is also the default export, so you could also do:

import it from 'param.macro'

The benefits of this explicit import are that linters and type systems won't have a fit over _ and it not being defined. It's also self-documenting and more easily understandable. Anyone looking at your code will know that these symbols come from param.macro.

set custom tokens

You can set custom identifiers for these just by using an aliased import.

import { it as IT, _ as PLACEHOLDER } from 'param.macro'

or for the default it export:

import IT from 'param.macro'

examples

lambda parameters

Scala, Kotlin, etc have what's called a lambda parameter — an easy shorthand for passing unary (single-argument) functions to other functions (higher order). It's useful in higher order functions like Array#map():

import it from 'param.macro'

const people = [
  { name: 'Jeff' },
  { name: 'Karen' },
  { name: 'Genevieve' }
]

people.map(it.name)
// -> ['Jeff', 'Karen', 'Genevieve']

argument placeholders

Transform this:

import { _ } from 'param.macro'

function sumOfThreeNumbers (x, y, z) {
  return x + y + z
}

const oneAndTwoPlusOther = sumOfThreeNumbers(1, 2, _)

... into this:

function sumOfThreeNumbers (x, y, z) {
  return x + y + z
}

const oneAndTwoPlusOther = _arg => {
  return sumOfThreeNumbers(1, 2, _arg)
}

_ and it in assignments

Most expressions using _ and it can also be used outside function calls and assigned to a variable. Here are some ultra simple cases to demonstrate this:

import { _, it } from 'param.macro'

const identity = it
const isEqualToItself = it === it

const areSameThing = _ === _

... becomes:

const identity = _it => _it
const isEqualToItself = _it2 => _it2 === _it2

const areSameThing = (_arg, _arg2) => _arg === _arg2

We could implement a hasOwn() function to check if a property exists on an object like this:

import { it, _ } from 'param.macro'

const hasOwn = it.hasOwnProperty(_)
const object = { flammable: true }

hasOwn(object, 'flammable')
// -> true

other expressions

You can also put these macros to use within binary expressions, template literals, and most other expressions.

import { it, _ } from 'param.macro'

const log = console.log(_)

log([0, 1, 0, 1].filter(!!it))
// -> [1, 1]

const heroes = [
  { name: 'bob', getPower () { return { level: 9001 } } },
  { name: 'joe', getPower () { return { level: 4500 } } }
]

log(heroes.find(it.getPower().level > 9000))
// -> { name: 'bob', getPower: [Function] }

const greet = `Hello, ${_}!`

log(greet('world'))
// -> Hello, world!

It's especially fun to use with the pipeline operator since it basically removes the need to auto-curry an entire library's API (like [Ramda][ramda]), which can be pretty costly for performance.

This is a scenario specifically tested against to ensure compatibility:

import { _, it } from 'param.macro'

const add = _ + _
const tenPlusString =
  it
  |> parseInt(_, 10)
  |> add(10, _)
  |> String

tenPlusString('10') |> console.log
// -> 20

lift modifier

In addition to _ and it, there is a third symbol exported by param.macro called lift. In most scenarios it is simply removed from the output but is very useful in combination with _ placeholders.

Because it creates only unary functions in place and _ always traverses out of its nearest parent function call, lift serves as an operator that fills out the middle ground: using placeholders to create inline functions of any arity.

With _ alone, the following example will not do what you probably want:

import { _ } from 'param.macro'

const array = [1, 2, 3, 4, 5]
const sum = array.reduce(_ + _)

Because it produces this:

const array = [1, 2, 3, 4, 5]
const sum = (_arg, _arg2) => {
  return array.reduce(_arg + _arg2)
}

To actually pass in an implicit binary function with _ you can use the lift operator:

import { _, lift } from 'param.macro'

const array = [1, 2, 3, 4, 5]
const sum = array.reduce(lift(_ + _))
console.log(sum)
// -> 15

It may be helpful to note that _ is still following its own rules here: it traversed upward out of its parent function call! It just so happens that call is removed afterward leaving your new function exactly where you want it.

usage

.babelrc.js (Babel v7)

module.exports = {
  presets: [],
  plugins: ['babel-plugin-macros']
}

.babelrc (Babel v6)

{
  "presets": [],
  "plugins": ["babel-plugin-macros"]
}

standalone plugin

A standalone version is also provided for those not already using babel-plugin-macros:

  • .babelrc.js (Babel v7)

    module.exports = {
      presets: [],
      plugins: ['module:param.macro/plugin']
    }
    
  • .babelrc (Babel v6)

    {
      "presets": [],
      "plugins": ["param.macro/plugin"]
    }
    

differences between _ and it

There are two main & distinct constructs provided by param.macro:

  • _ → partial application symbol
  • it → implicit parameter symbol

There are a couple of major differences between the two:

scoping

_ will always traverse upward out of the nearest function call, while it will be transformed in place. It's easiest to see when we look at a simple example:

import { _, it } from 'param.macro'

const array = [1, 2, 3]
array.map(_)
array.map(it)

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

const array = [1, 2, 3]
_arg => array.map(_arg)
array.map(_it => _it)

An exception to these scoping differences is at the top-level, like the right-hand side of an assignment. it and _ behave similarly here since there's no further upward to go, so they'll both happen to target the same place.

For example the following two map implementations do the same thing:

import { _, it } from 'param.macro'

const map1 = _.map(_)
const map2 = it.map(_)

However, if nested deeper inside a function call the object placeholder _ in map1 above would traverse further upward than an it would, and create a separate function first, before the argument placeholder _ inside the method call itself. This creates an unary method call instead of the implicit binary function we probably wanted, lift or not.

The it implementation in map2 above does still create the implicit binary function, even if nested deeper. And following the normal placeholder rules, any _ inside the method call will traverse up to the method call and stop to create a function there, as we wanted.

argument reuse

it always refers to the same argument even when used multiple times in an argument list. _ will always refer to the next argument.

import { _, it } from 'param.macro'

console.log(_ + _ + _)
console.log(it + it + it)

... are compiled to:

(_arg, _arg2, _arg3) => console.log(_arg + _arg2 + _arg3)
console.log(_it => _it + _it + _it)

caveats & limitations

_ is a comm

View on GitHub
GitHub Stars184
CategoryDevelopment
Updated2mo ago
Forks8

Languages

JavaScript

Security Score

100/100

Audited on Jan 29, 2026

No findings