Skip to content

Instantly share code, notes, and snippets.

@zmaril
Last active September 30, 2015 02:17
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 zmaril/1705450 to your computer and use it in GitHub Desktop.
Save zmaril/1705450 to your computer and use it in GitHub Desktop.
Turning any number into +[]'s with javascript
"use strict";
// This is one of my favorite javascript tricks. It shows off some of the weird
// parts of the language in a pretty straightforward way. With only three
// characaters, '+', '[' and ']', we can encode any natural number we want in javascript. For
// the math minded, this was inspiried by Peano's Axioms and javascript's type conversions.
// http://mathworld.wolfram.com/PeanosAxioms.html
// This file uses es6, run it by doing `node --harmony peano.js`
// The unary plus (calling plus with one argument) converts the single argument
// to an equivalent number. Per the javascript standard, an empty array converts
// to 0 and an array with a single element recursively calls the type conversion
// on the element inside. An array with multiple elements returns a NaN. Note
// that we are using strict equality here, `===`, and that this doesn't rely on
// weird string type conversions using `==`.
for (let i = 0; i < 10; i++){
console.assert(i === +[i]);
console.assert(isNaN(+[i,i]));
}
// Next, we use the ++value operator, which just increments the passed in value.
// We need to use the `++` operator on the left side of the value, because `++`
// on the left increments the value before it is used in the expression while
// `++` on the right increments the value after it has been used in the
// expression.
for (let i = 0; i < 10; i++){
console.assert(i+1 === ++i);
console.assert(i+1 !== i++);
}
// Now, we see that we have a way of representing zero with our restricted
// character set, as well as incrementing any value. However, it is a bit tricky
// to combine them because of how javascript is parsed.
try { eval("+++[]"); console.log("Sadly, \"++[]\" is a parse error and this isn't printed.");} catch (err){};
// So, we can use a little slight of hand here. By putting a value into an array
// and pulling it back out afterwards, the value stays the same, but we start
// with a fresh slate in terms of syntax. Thankfully, javascript uses zero based
// indexing, so we can use our previous representation of zero to pull the value
// back out of the array via the `value[i]` syntax. Furthermore, and this is
// pure luck, array access via bracket notation takes higher precendence than
// the increment operation.
for (let i = 0; i < 10; i++){
console.assert(i === [i][0]);
console.assert(i === [i][+[]]);
console.assert(i+1 === ++[i][+[]]);
}
//So, by wrapping a value up like "++[value][+[]]" we can increment the value by
//one. Thankfully this composes, but now we are to the point where we are
//creating strings of code within our code that need to be evaluated. So, we'll
//just use `eval` on the results of the increments and it works!
function increment(s) {
return "++["+ s +"][+[]]";
};
for (let i = 0; i < 10; i++){
console.assert(i+1 === eval(increment(i)));
console.assert(i+2 === eval(increment(increment(i))));
console.assert(i+3 === eval(increment(increment(increment(i)))));
}
// So, finally, we are at the point where we can take in any natural number and
// return a representation of it encoded in only '+','[' and ']'.
function encode(n){
let s = "+[]";
for (let i = 0 ; i < n ; i++){
s = increment(s);
}
return s;
};
for (let i = 0; i < 10; i++){
console.assert(i === eval(encode(i)));
}
// And that's how you can create javascript code strictly recreates naturally numbers.
for (let i = 0; i < 10; i++){
console.log("Encoding "+ i);
console.log(encode(i));
console.log("\n");
}
// If you relax the constraint that you strictly encode a natural number and
// rely on loose type equality, you can compress the representation down by
// encoding each digit and creating a string instead. There are a few more type
// tricks in the compress function, but they aren't so complicated to figure out
// compared to unary plus. By trading strict equalitly for loose, we get a
// logarithmic decrease in representation size.
function compress (n){
return String(n).split("").map(encode).map(v => "[" + v + "]").join("+");
};
for (let i = 0; i < 10; i++ ){
console.assert(i === eval(encode(i)));
}
for (let i = 0; i < 100; i += 11){
console.log("Compressing "+ i);
console.log(compress(i));
console.log("\n");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment