GithubHelp home page GithubHelp logo

krzkaczor / babel-plugin-tailcall-optimization Goto Github PK

View Code? Open in Web Editor NEW
191.0 13.0 9.0 78 KB

Tail call optimization for JavaScript!

License: MIT License

JavaScript 100.00%
babel javascript tail-call-optimization

babel-plugin-tailcall-optimization's Introduction

babel-plugin-tailcall-optimization

JavaScript Style Guide

Tail call optimization for JavaScript!

Installation

npm install babel-plugin-tailcall-optimization --save-dev

and add to your .babelrc:

"plugins": ["tailcall-optimization"]

if you use babel@6 use babel-plugin-tailcall-optimization@1 package

How does it work?

We rewrite functions with tail calls to ones using while loops. Original function with tail call:

function counter (n, acc = 0) {
  if (n === 0) {
    return acc
  } else {
    return counter(n - 1, acc + 1)
  }
}

gets rewritten to this:

function counter(n, acc = 0) {
  var _repeat = true;

  var _n, _acc;

  while (_repeat) {
    _repeat = false;

    if (n === 0) {
      return acc;
    } else {
      _n = n - 1
      _acc = acc + 1
      n = _n
      acc = _acc
      _repeat = true;
      continue;
    }
  }
}

Plugin does not affect functions without TCOs so it's safe to use.

Benchmarks

For Fibonacci Sequence example benchmark.js results are:

Fibonacci Sequence without TCO x 270,170 ops/sec ±1.14% (85 runs sampled)
Fibonacci Sequence with TCO x 1,298,276 ops/sec ±1.24% (83 runs sampled)

So function after TCO optimization is almost 5 times faster.

Benchmark code

Known issues

  • Currently when plugin detects function creation within tailcalled function it does not optimize it. It's related to difficulties in implementation (function scoping rules). Read more: https://phabricator.babeljs.io/T6869

  • It does not work for mutual recursive functions. I guess it's not super big problem - even JVM does not do this.

babel-plugin-tailcall-optimization's People

Contributors

acthp avatar amilajack avatar andreineculau avatar gtkatakura avatar krzkaczor avatar mfila 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  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

babel-plugin-tailcall-optimization's Issues

Plugin breaks on `export default function` due to null pointer

If you try to compile something with this basic setup, the plugin fails:

.babelrc:

{
  "presets": ["es2015"],
  "plugins": ["tailcall-optimization"]
}

test.js:

 export default function(foo) { console.log(foo); }

And you will receive an error about name not being found on the id of path.node.

Discussion of adding TCO when closure is detected

When closure is detected what about pre-processing using a combination of copying params/var decelerations to a state object (when those refs are used via closure) in the function and update all refs to point to this state.

And converting all declared functions in the closure to automatically invoked curried functions that apply the state ref via closure.

I believe this would fix any issues related to function scoping rules including referencing of primitive datatypes since the automatically invoked curried functions would be applied with object refs and would keep that reference even if the parent scope re-assigned a new object ref to the state object variable

Basically a statement like this

function someMethod(min, b) {
  const val = b.find(s => s.min < min)
   ...
  return someCondition ? someMethod(val.min, someStatementOf(val)) : val.min
}

would convert to something like this

function someMethod(min, b) {
  var _b, _min
  ...
  while(true){
   // at start of each iteration assign _state to a new object with updated values
   // this allows for automatically invoked curried functions that were applied in a previous iteration to 
   // keep ref to that object while the next iteration scope can work cleanly on the new object with 
   // updated values 
   const _state = {
     min,
   }
   const  val = b.find((state => s => s.min < state['min'])(_state))
   if(someCondition){
        _min = val.min
      _b = someStatementOf(val)
      b = _b
      min = _min
      continue;
    }
    else {
      return val.min
    }
  }
}

Can a Babel 7 release be awaited?

  • In #13 efforts have been made to support Babel 7.
  • However no release on NPM registry is to be found.
  • Cannot install next branch directly from git repo via NPM, because either the build step or it is me who is missing a step here. (I don't consider this a problem that generated code is missing from the git repo, just a fact.)

Questions:

  1. Can I hope for a Babel 7 compatible NPM release? (And when?)
  2. Should I fork & publish one if I need it? (Would you mind it?)

Doesn't handle the ternary operator

OS: Linux (Arch)
Node.js: v7.4.0
babel-cli: 6.23.0 (babel-core 6.23.1)
babel-plugin-tailcall-optimization: 1.0.11


The if/else case is correctly handled e.g.:

Input

function factorial (n) {
    return factorialAccum(1, n)
}

function factorialAccum (accum, n) {
    if (n === 1) {
        return accum
    } else {
        return factorialAccum(accum * n, n - 1)
    }
}

console.log(factorial(5))

Output

"use strict";

function factorial(n) {
    return factorialAccum(1, n);
}

function factorialAccum(accum, n) {
    var _repeat = true;

    var _accum, _n;

    while (_repeat) {
        _repeat = false;

        if (n === 1) {
            return accum;
        } else {
            _accum = accum * n;
            _n = n - 1;
            accum = _accum;
            n = _n;
            _repeat = true;
            continue;
        }
    }
}

console.log(factorial(5));

But the equivalent code written with the ternary operator isn't transformed:

Input

function factorial (n) {
    return factorialAccum(1, n)
}

function factorialAccum (accum, n) {
    return n === 1 ? accum : factorialAccum(accum * n, n - 1)
}

console.log(factorial(5))

Output

"use strict";

function factorial(n) {
    return factorialAccum(1, n);
}

function factorialAccum(accum, n) {
    return n === 1 ? accum : factorialAccum(accum * n, n - 1);
}

console.log(factorial(5));

dependencies that should be dev

Shouldn't some of these dependencies be in fact devDependencies?

  "dependencies": {
    "babel-cli": "^6.11.0",
    "babel-core": "^6.13.0",
    "babel-preset-es2015": "^6.13.0",
    "babel-traverse": "^6.13.0",
    "babylon": "^6.8.0"
  },

Or else babel-plugin-tailcall-optimization ends up as a heavy dependency.

conditional operator not supported

Great plugin. Thanks for writing. Are conditionals supposed to be supported?

This sample input

const countJumps = (list, index, step) => {
  const value = list.get(index);

  if (value === undefined) return step;

  return countJumps(list.set(index, value + 1), index + value, step + 1);
};

const countJumpsTernary = (list, index, step) => {
  const value = list.get(index);

  return value === undefined
    ? step
    : countJumps(list.set(index, value + 1), index + value, step + 1);
};

Produces this output

const countJumps = (list, index, step) => {
  var _repeat = true;

  var _list, _index, _step;

  while (_repeat) {
    _repeat = false;

    const value = list.get(index);

    if (value === undefined) return step;

    _list = list.set(index, value + 1);
    _index = index + value;
    _step = step + 1;
    list = _list;
    index = _index;
    step = _step;
    _repeat = true;
    continue;
  }
};

const countJumpsTernary = (list, index, step) => {
  const value = list.get(index);

  return value === undefined ? step : countJumps(list.set(index, value + 1), index + value, step + 1);
};

Is this the expected behavior?
I'm using a fresh install of babel with "tailcall-optimization" as my only plugin.

does not work with concise body syntax (implied return) in arrow function

const counter = (n, acc = 0) => { return n === 0 ? acc : counter(n - 1, acc + 1) };

transpiles to:

const counter = (n, acc = 0) => {
  var _repeat = true;

  var _n, _acc;

  while (_repeat) {
    _repeat = false;

    if (n === 0) {
      return acc;
    } else {
      _n = n - 1;
      _acc = acc + 1;
      n = _n;
      acc = _acc;
      _repeat = true;
      continue;
    }
  }
};

but

const counter = (n, acc = 0) => n === 0 ? acc : counter(n - 1, acc + 1);

does not get changed

How the conversion is helping in TCO ?

as far as I know (which is very limited I guess 😁 ) the previous stack should be deleted but , how the conversion which you mentioned in the docs is helping with the TCO ?
What I understood from the samples is that it is converting the recursion into an iterating while loop ri8 !
It is overcoming the stack error but Is it TCO ?
Please correct me if I am wrong !...
Thanks

invalid code generated due to missing semicolons

Run-time error:

TypeError: _bpp is not a function

See _bpp in the generated code, below.

Input:

function toScreen(bpp, [next, ...rest], offset, acc) {
	if (!next) {
		return acc;
	}
	var [start, end] = next,
		len = (end - start + 1) / bpp;
	return toScreen(bpp, rest, len + offset, acc.concat([[offset, len + offset]]));
}

output:

"use strict";

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();

function _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); }

function toScreen(bpp, _ref, offset, acc) {
	var _ref2 = _toArray(_ref);

	var next = _ref2[0];

	var rest = _ref2.slice(1);

	var _repeat = true;

	var _bpp, _temp, _offset, _acc;

	while (_repeat) {
		var _temp2, _temp3;

		_repeat = false;

		if (!next) {
			return acc;
		}
		var _next = next;

		var _next2 = _slicedToArray(_next, 2);

		var start = _next2[0];
		var end = _next2[1];
		var len = (end - start + 1) / bpp;_bpp = bpp
		_temp = rest
		_offset = len + offset
		_acc = acc.concat([[offset, len + offset]])
		bpp = _bpp
		(_temp2 = _temp, _temp3 = _toArray(_temp2), next = _temp3[0], rest = _temp3.slice(1), _temp2)
		offset = _offset
		acc = _acc
		_repeat = true;
		continue;
	}
}

Lack of semicolons results in this parsing as a function call.

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.