Giter Club home page Giter Club logo

babel-plugin-partial-application's Introduction

babel-plugin-partial-application · Version License Travis CI LightScript Gitter

deprecated

Please use param.macro instead as it's a more focused, stable, and explicit implementation of this concept. All development on babel-plugin-partial-application has ceased and I'd heavily recommend using param.macro instead! No fixes, additions, or updates will be made here — regardless of severity.


Partial application syntax for JavaScript, inspired by Scala's _ & Kotlin's it.

If you like this plugin, go to @rbuckton's TC39 proposal to add syntactic partial application to JavaScript - star it to show support, start conversations to improve it, and share it to make it happen.

The proposal is here: https://github.com/rbuckton/proposal-partial-application


overview

Use the _ symbol ( or a custom identifier of your choosing ) as a placeholder to signal that a function call is partially applied, or that you'll provide the object of a method call at a later time.

So basically - the original code isn't actually called yet, but will return a new function receiving the arguments you signified as placeholders.

You can provide one or several placeholders mixed in with the rest of the usual arguments. Think of the values that aren't placeholders as being "bound". Check out the examples section to see all the different ways this is useful.

installation

Before you install this, note that this project is no longer maintained or supported, and it's highly recommended you use its successor param.macro instead.

npm i --save-dev babel-plugin-partial-application

Make sure you also have Babel installed:

npm i --save-dev babel-cli

examples

basic placeholders

Transform this:

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)
}

It also works for method calls, where this:

const hasOwn = {}.hasOwnProperty.call(_, _)

... becomes:

const hasOwn = (_arg, _arg2) => {
  return {}.hasOwnProperty.call(_arg, _arg2)
}

spread placeholders

You can also use spread to represent multiple arguments:

const maxOf = Math.max(..._)

maxOf(1, 2, 3, 4, 5)
// -> 5

If your target environment doesn't support rest / spread, you'll have to transpile it separately as usual.

object placeholders

The placeholder can stand in for an object on which you'll access properties or call methods.

As an example, we could re-implement the hasOwn() function from the basic placeholders section like this (although without .call() this time):

const hasOwn = _.hasOwnProperty(_)

... which compiles to:

const hasOwn = (_obj, _arg) => {
  return _obj.hasOwnProperty(_arg)
}

The object that will replace the placeholder becomes the first argument to the resulting function, so you'd use it like this:

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

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

lambda parameters

When used in an argument list, an object placeholder basically becomes what Scala et al. call a lambda parameter - an easy shorthand for accessing properties or calling methods on the applied argument. It's useful in higher order functions like Array#map():

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

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

positional placeholders

Sometimes called swizzle or rearg, you can partially apply arguments in a different order than the function normally accepts, or use a single argument multiple times.

Let's say you want to reorder the arguments of lodash.get(), which normally has a signature of object, path, defaultValue, to path, defaultValue, object:

// forget that lodash/fp even exists for a second :)
import { get } from 'lodash'

// reorder the arguments so the data is accepted last
const getFunctional = get(_`3`, _`1`, _`2`)

getFunctional('color', 'blue', { color: 'green' })
// -> 'green'

You can also use positional placeholders to reuse a single argument, for example to create a function checking if something is equal to itself ( NaN never is ):

const isSameThing = _`1` === _`1`

isSameThing('yes!')
// -> true

isSameThing(NaN)
// -> false

binary expressions

You can use placeholders within logical comparisons ( === ) or addition, subtraction, string concatenation, etc.

array.filter(_ === true)

For a classic example, let's say you have an Array of numbers and want the total sum of them all:

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

You can combine this with object placeholders and lambda parameters:

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

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

default parameters

In places where you might use a placeholder or a lambda parameter, you can use assignment syntax to set a default parameter:

const stringify = JSON.stringify(_, null, _ = 2)

This compiles to a standard JavaScript default parameter so the default would be used under the same circumstances, ie. when the argument is either undefined or absent. Compare the output of these:

stringify({ foo: 'bar' })
stringify({ foo: 'bar' }, '>>>>')
{
  "foo": "bar"
}

vs.

{
>>>>"foo": "bar"
}

Default parameters are also possible with member expressions:

const greet = name => console.log(`Hello, ${name}!`)

const sayHelloToPerson = greet(_.name = 'world')

sayHelloToPerson({ name: 'Arlene' })
// -> Hello, Arlene!

sayHelloToPerson({})
// -> Hello, world!

Note that no safeguards are added - so in this case, sayHelloToPerson() and sayHelloToPerson(null) would cause an error.

template literals

You can use almost all the features above inside template literals:

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

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

const greet2 = `Hello, ${_.name}!`

greet2({ name: 'Tom' })
// -> Hello, Tom!

set a custom token

If you happen to need _ as an identifier for whatever reason, there are two different ways to set a custom placeholder token.

You can either use options in your Babel config as described below or simply import / require the plugin:

import it from 'babel-plugin-partial-application'
const it = require('babel-plugin-partial-application')

This import is removed from the compiled output - so you don't need a production dependency on the plugin. It won't actually import anything at runtime.

Whatever identifier you set the import to is what will be used as the placeholder, so all of the following would work:

import __ from 'babel-plugin-partial-application'

const greet = `Hello, ${__}!`
import $ from 'babel-plugin-partial-application'

const greet = `Hello, ${$}!`
import PLACEHOLDER from 'babel-plugin-partial-application'

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

The benefit of this method over .babelrc configuration is that linters and type systems won't have a fit because _ isn't defined. It's also more explicit, more easily understandable, and self-documenting. Anyone looking at your code will know that _ is from babel-plugin-partial-application.

curried-style functions

A handy usage for this plugin is emulating "curried" style functions, where a function returns another function that receives the data before finally returning the result.

While partial application is a different thing, it can accomplish the same goals in a lot of situations. For example, this:

import { map, get } from 'lodash'

const mapper = map(_, get(_, 'nested.key', 'default'))

... would compile to this:

import { map, get } from 'lodash'

const mapper = _arg => {
  return map(_arg, _arg2 => {
    return get(_arg2, 'nested.key', 'default')
  })
}

... to be used something like this:

const array = [
  { nested: { key: 'value' } },
  { nested: { something: '' } },
  { nested: { key: 'things' } }
]

const newArray = mapper(array)
// -> ['value', 'default', 'things']

usage

.babelrc

{
  "presets": [],
  "plugins": ["partial-application"]
}

Optionally configure the plugin by using an Array of [pluginName, optionsObject]. This is the default configuration:

{
  "presets": [],
  "plugins": [
    ["partial-application", {
      "placeholder": "_",
      "useAlternatePlaceholder": false
    }]
  ]
}
property type default description
placeholder String _ Identifier used to signal partial application in function calls.
useAlternatePlaceholder Boolean false Use __ as the placeholder. Ignored if placeholder is set to a custom value.

You can also set a custom placeholder by importing or requiring the plugin. See "set a custom token" above for usage.

Babel CLI

babel --plugins partial-application src.js

See Babel's CLI documentation for more.

Babel API

require('babel-core').transform('code', {
  presets: [],
  plugins: ['partial-application']
})

caveats & limitations

_ is a common variable name ( eg. for lodash )

This is the most obvious potential pitfall when using this plugin. _ is commonly used as the identifier for things like lodash's collection of utilities.

This would be perfectly valid normally, but by default would cause an error when using this plugin:

import _ from 'lodash'

// -> src.js: Cannot use placeholder as an identifier.

There are a few reasons this is not seen as problematic.

  1. _ is a common symbol for partial application

The Scala language uses the underscore as a placeholder for partially applied functions, and tons of JavaScript libraries have used it as well - so it's become recognizable.

  1. Monolithic builds of packages like lodash are on the way out

lodash v5 will be getting rid of the monolithic build in favor of explicitly imported or 'cherry-picked' utilities. So it will become less common to see the entirety of lodash imported, especially with ES module tree-shaking on the horizon.

On top of that, babel-plugin-lodash still works effectively when you just import what you need like so:

import { add } from 'lodash'
  1. The plugin allows for custom placeholder symbols

If you do happen to need _ as an identifier, you're able to change the placeholder to any string value you want. Right now this plugin doesn't place limitations on that, although obvious keywords won't make the cut beyond the plugin.

You could use $, it, or even PLACEHOLDER - though I think you'll understand why the _ is an appealing choice over the alternatives.

  1. Partial application with _ is damn cool

comparison to libraries

Lodash, Underscore, Ramda, and other libraries have provided partial application with a helper function something like _.partial(fn, _) which wraps the provided function, and basically just takes advantage of the fact that {} !== {} to recognize that the monolithic _, _.partial.placeholder, or Ramda's R.__ is a specific object deemed a placeholder.

This Babel plugin gives you the same features at the syntax level. Or even better, like lambda parameters and object placeholders, eat your heart out lodash 😉. And it all comes with no runtime overhead. If you don't use placeholders your functions are unaffected. If you do, they're compiled away and turn into regular functions that don't have to check arguments to see if a placeholder was provided.

see also

contributing

This project is no longer maintained or supported, so please check out its successor param.macro.

license

MIT © Bo Lingen / citycide

babel-plugin-partial-application's People

Contributors

haltcase avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

5310 d3dc

babel-plugin-partial-application's Issues

Using one argument few times

It will be nice to have ability to use an argument few times in function body.

For example, we want to create isNaN function, which looks like this in classic JavaScript:

const isNaN = x => x !== x

So we want to write something like this:

const isNaN = _ !== _

But it will be compiled into

const isNaN = (_a, _b) => {
  return _a !== _b
}

Not into

const isNaN = _a => {
  return _a !== _a
}

This ability requires new syntax. For example Clojure anonymous function syntax:

(def isNaN #(= % %))

Or:

(def isNaN #(= %1 %1))

In this Clojure examples we use % for 1-arity functions, and %1, %2, ..., %n for n-arity functions.
Rewritten with this syntax example:

const isNaN = % !== %

And cases when we need more than one argument:

const array = [1, 2, 3, 4, 5]
array.reduce(%1 + %2)
// -> 15

Of course not necessary use % as placeholder, I use it just to keep a parallel with Clojure.

Deprecation in favor of `param.macro`

I've decided to deprecate this package in favor of my newer project param.macro. It's more focused, explicit, and stable, though it does intentionally lack a few of the features from this plugin to achieve those things. It has the same online playground as this plugin so you can compare it immediately in your browser.

The main difference is that the feature set has been split into two symbols instead of one, _ and it. This is phenomenally easier to handle (param.macro uses far less code than this plugin) and also easier to understand. There are clear semantic differences between the two. From param.macro's readme:

_ will always traverse upward out of the nearest function call, while it will be transformed in place.

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

Features supported by both:

  • lambda parameters ( using a separate it symbol )
  • argument placeholders ( using _ )
  • custom tokens ( using aliased imports )
  • arbitrary expressions & assignment ( object placeholders, template literals, binary expressions, etc. )

Features not intended for param.macro:

In the interest of readable, easily understandable code these features are intentionally left out.

  • positional placeholders
  • default parameters
  • usage without import

Features being considered for param.macro:

  • spread placeholder

If anyone has questions on the differences or how to migrate let me know, but the readme and the playground should help a lot with that.

Repo / npm package takeover

If you're interested in taking over the GitHub repo and the npm package, hit me up.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.