Bug 947501: Uplift Add-on SDK to Firefox. r=me

11344b2...dc6000a
This commit is contained in:
Dave Townsend 2013-12-06 15:53:30 -08:00
parent e0a766081b
commit d7ad2054c1
5 changed files with 719 additions and 137 deletions

View File

@ -35,13 +35,13 @@ geolocation API in Firefox.
## Using Geolocation in an Add-on ##
Suppose we want to use the
[geolocation API built into Firefox](https://developer.mozilla.org/en/using_geolocation).
[geolocation API built into Firefox](https://developer.mozilla.org/en-US/docs/WebAPI/Using_geolocation).
The SDK doesn't provide an API to access geolocation, but we can
[access the underlying XPCOM API using `require("chrome")`](dev-guide/guides/xul-migration.html#xpcom).
The following add-on adds a [button to the toolbar](dev-guide/tutorials/adding-toolbar-button.html):
when the user clicks the button, it loads the
[XPCOM nsIDOMGeoGeolocation](https://developer.mozilla.org/en/XPCOM_Interface_Reference/NsIDOMGeoGeolocation)
[XPCOM nsIDOMGeoGeolocation](https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/NsIDOMGeoGeolocation)
object, and retrieves the user's current position:
var {Cc, Ci} = require("chrome");
@ -88,7 +88,7 @@ info: longitude: 93.0785269
</pre>
So far, so good. But the geolocation guide on MDN tells us that we must
[ask the user for permission](https://developer.mozilla.org/en/using_geolocation#Prompting_for_permission)
[ask the user for permission](https://developer.mozilla.org/en-US/docs/WebAPI/Using_geolocation#Prompting_for_permission)
before using the API.
So we'll extend the add-on to include an adapted version of the code in

View File

@ -13,15 +13,17 @@ When the method is invoked on an instance of the object, the original
function is called. It is passed the object instance (i.e. `this`) as
the first parameter, followed by any parameters passed into the method.
let { method } = require("sdk/lang/functional");
let myNumber = {
const { method } = require("sdk/lang/functional");
const times = (target, x) => target.number *= x;
const add = (target, x) => target.number += x;
const myNumber = {
times: method(times),
add: method(add),
number: 0
};
function times (target, x) { return target.number *= x; }
function add (target, x) { return target.number += x; }
console.log(myNumber.number); // 0
myNumber.add(10); // 10
@ -44,8 +46,8 @@ wait (i.e. `setTimeout(function () { ... }, 0)`), except that the wrapped functi
may be reused and does not need to be repeated each time. This also enables you
to use these functions as event listeners.
let { defer } = require("sdk/lang/functional");
let fn = defer(function myEvent (event, value) {
const { defer } = require("sdk/lang/functional");
const fn = defer((event, value) => {
console.log(event + " : " + value);
});
@ -74,16 +76,11 @@ An alias for [defer](modules/sdk/lang/functional.html#defer(fn)).
Invokes `callee`, passing `params` as an argument and `self` as `this`.
Returns the value that is returned by `callee`.
let { invoke } = require("sdk/lang/functional");
const { invoke } = require("sdk/lang/functional");
const sum = (...args) => args.reduce((a, b) => a + b);
invoke(sum, [1,2,3,4,5], null); // 15
function sum () {
return Array.slice(arguments).reduce(function (a, b) {
return a + b;
});
}
@param callee {function}
Function to invoke.
@param params {Array}
@ -98,9 +95,9 @@ Returns the value that is returned by `callee`.
@function
Takes a function and bind values to one or more arguments, returning a new function of smaller arity.
let { partial } = require("sdk/lang/functional");
let add = function add (x, y) { return x + y; }
let addOne = partial(add, 1);
const { partial } = require("sdk/lang/functional");
const add = (x, y) => x + y;
const addOne = partial(add, 1);
addOne(5); // 6
addOne(10); // 11
@ -122,14 +119,16 @@ Returns the [composition](http://en.wikipedia.org/wiki/Function_composition_(com
return value of the function that follows. In math terms, composing the functions
`f()`, `g()`, and `h()` produces `f(g(h()))`.
let { compose } = require("sdk/lang/functional");
const { compose } = require("sdk/lang/functional");
let welcome = compose(exclaim, greet);
const square = x => x * x;
const increment = x => x + 1;
welcome('moe'); // "hi: moe!";
const f1 = compose(increment, square);
f1(5); // => 26
function greet (name) { return "hi: " + name; }
function exclaim (statement) { return statement + "!"; }
const f2 = compose(square, increment);
f2(5); // => 36
@param fn... {function}
Takes a variable number of functions as arguments and composes them from right to left.
@ -144,16 +143,14 @@ Returns the first function passed as an argument to the second,
allowing you to adjust arguments, run code before and after, and
conditionally execute the original function.
let { wrap } = require("sdk/lang/functional");
const { wrap } = require("sdk/lang/functional");
let wrappedHello = wrap(hello, function (fn, name) {
return "before, " + fn(name) + "after";
});
const hello = name => "hello: " + name;
const wrappedHello = wrap(hello, (fn, name) =>
"before, " + fn(name) + "after");
wrappedHello("moe"); // "before, hello: moe, after"
function hello (name) { return "hello: " + name; }
@param fn {function}
The function to be passed into the `wrapper` function.
@ -170,8 +167,8 @@ conditionally execute the original function.
@function
Returns the same value that is used as the argument. In math: f(x) = x.
let { identity } = require("sdk/lang/functional");
let x = 5;
const { identity } = require("sdk/lang/functional");
const x = 5;
identity(x); // 5
@param value {mixed}
@ -190,15 +187,15 @@ storing the result, based on the arguments to the original function. The
default `hashFunction` just uses the first argument to the memoized function as
the key.
let { memoize } = require("sdk/lang/functional");
const { memoize } = require("sdk/lang/functional");
let memoizedFn = memoize(primeFactorization);
const memoizedFn = memoize(primeFactorization);
memoizedFn(50); // Returns [2, 5, 5], had to compute
memoizedFn(100); // Returns [2, 2, 5, 5], had to compute
memoizedFn(50); // Returns [2, 5, 5] again, but pulled from cache
function primeFactorization (x) {
const primeFactorization = x => {
// Some tricky stuff
}
@ -209,16 +206,14 @@ the key.
// function will just parse the last name, as our naive
// implementation assumes that they will share the same lineage
let getLineage = memoize(function (name) {
const getLineage = memoize(name => {
// computes lineage
return data;
}, hasher);
// Hashing function takes a string of first and last name
// and returns the last name.
function hasher (input) {
return input.split(" ")[1];
}
const hasher = input => input.split(" ")[1];
getLineage("homer simpson"); // Computes and returns information for "simpson"
getLineage("lisa simpson"); // Returns cached for "simpson"
@ -240,12 +235,11 @@ Much like `setTimeout`, `delay` invokes a function after waiting a set number of
milliseconds. If you pass additional, optional, arguments, they will be forwarded
on to the function when it is invoked.
let { delay } = require("sdk/lang/functional");
const { delay } = require("sdk/lang/functional");
const printAdd = (a, b) console.log(a + "+" + b + "=" + (a+b));
delay(printAdd, 2000, 5, 10);
// Prints "5+10=15" in two seconds (2000ms)
function printAdd (a, b) { console.log(a + "+" + b + "=" + (a+b)); }
@param fn {function}
A function to be delayed.
@ -264,8 +258,8 @@ Repeated calls to the modified function will have no effect, returning
the value from the original call. Useful for initialization functions, instead
of having to set a boolean flag and checking it later.
let { once } = require("sdk/lang/functional");
let setup = once(function (env) {
const { once } = require("sdk/lang/functional");
const setup = once(env => {
// initializing important things
console.log("successfully initialized " + env);
return 1; // Assume success and return 1
@ -286,16 +280,156 @@ of having to set a boolean flag and checking it later.
The wrapped `fn` that can only be executed once.
</api>
<api name="chain">
<api name="cache">
@function
An alias for [once](modules/sdk/lang/functional.html#once(fn)).
</api>
<api name="complement">
@function
Takes a `f` function and returns a function that takes the same
arguments as `f`, has the same effects, if any, and returns the
opposite truth value.
const { complement } = require("sdk/lang/functional");
let isOdd = x => Boolean(x % 2);
isOdd(1) // => true
isOdd(2) // => false
let isEven = complement(isOdd);
isEven(1) // => false
isEven(2) // => true
@param lambda {function}
The function to compose from
@returns {boolean}
`!lambda(...)`
</api>
<api name="constant">
@function
Constructs function that returns `x` no matter what is it
invoked with.
const { constant } = require("sdk/lang/functional");
const one = constant(1);
one(); // => 1
one(2); // => 1
one.apply({}, 3); // => 1
@param x {object}
Value that will be returned by composed function
@returns {function}
</api>
<api name="apply">
@function
Apply function that behaves like `apply` in other functional
languages:
const { apply } = require("sdk/lang/functional");
const dashify = (...args) => args.join("-");
apply(dashify, 1, [2, 3]); // => "1-2-3"
apply(dashify, "a"); // => "a"
apply(dashify, ["a", "b"]); // => "a-b"
apply(dashify, ["a", "b"], "c"); // => "a,b-c"
apply(dashify, [1, 2], [3, 4]); // => "1,2-3-4"
@param f {function}
function to be invoked
</api>
<api name="flip">
@function
Returns function identical to given `f` but with flipped order
of arguments.
const { flip } = require("sdk/lang/functional");
const append = (left, right) => left + " " + right;
const prepend = flip(append);
append("hello", "world") // => "hello world"
prepend("hello", "world") // => "world hello"
@param f {function}
function whose arguments should to be flipped
@returns {function}
function with flipped arguments
</api>
<api name="when">
@function
Takes `p` predicate, `consequent` function and an optional
`alternate` function and composes function that returns
application of arguments over `consequent` if application over
`p` is `true` otherwise returns application over `alternate`.
If `alternate` is not a function returns `undefined`.
const { when } = require("sdk/lang/functional");
function Point(x, y) {
this.x = x
this.y = y
}
const isPoint = x => x instanceof Point;
const inc = when(isPoint, ({x, y}) => new Point(x + 1, y + 1));
inc({}); // => undefined
inc(new Point(0, 0)); // => { x: 1, y: 1 }
const axis = when(isPoint,
({ x, y }) => [x, y],
_ => [0, 0]);
axis(new Point(1, 4)); // => [1, 4]
axis({ foo: "bar" }); // => [0, 0]
@param p {function}
predicate function whose return value determines to which
function be delegated control.
@param consequent {function}
function to which arguments are applied if `predicate` returned
`true`.
@param alternate {function}
function to which arguments are applied if `predicate` returned
`false`.
@returns {object|string|number|function}
Return value from `consequent` if `p` returned `true` or return
value from `alternate` if `p` returned `false`. If `alternate`
is not provided and `p` returned `false` then `undefined` is
returned.
</api>
<api name="chainable">
@function
Creates a version of the input function that will return `this`.
let { chain } = require("sdk/lang/functional");
const { chainable } = require("sdk/lang/functional");
function Person (age) { this.age = age; }
Person.prototype.happyBirthday = chain(function () this.age++);
Person.prototype.happyBirthday = chainable(function() {
return this.age++
});
let person = new Person(30);
const person = new Person(30);
person
.happyBirthday()
@ -311,7 +445,126 @@ Creates a version of the input function that will return `this`.
The wrapped function that executes `fn` and returns `this`.
</api>
<api name="cache">
<api name="field">
@function
An alias for [once](modules/sdk/lang/functional.html#once(fn)).
Takes field `name` and `target` and returns value of that field.
If `target` is `null` or `undefined` it would be returned back
instead of attempt to access it's field. Function is implicitly
curried, this allows accessor function generation by calling it
with only `name` argument.
const { field } = require("sdk/lang/functional");
field("x", { x: 1, y: 2}); // => 1
field("x")({ x: 1 }); // => 1
field("x", { y: 2 }); // => undefiend
const getX = field("x");
getX({ x: 1 }); // => 1
getX({ y: 1 }); // => undefined
getX(null); // => null
@param name {string}
Name of the field to be returned
@param target {object}
Target to get a field by the given `name` from
@returns {object|function|string|number|boolean}
Field value
</api>
<api name="query">
@function
Takes `.` delimited string representing `path` to a nested field
and a `target` to get it from. For convinience function is
implicitly curried, there for accessors can be created by invoking
it with just a `path` argument.
const { query } = require("sdk/lang/functional");
query("x", { x: 1, y: 2}); // => 1
query("top.x", { x: 1 }); // => undefiend
query("top.x", { top: { x: 2 } }); // => 2
const topX = query("top.x");
topX({ top: { x: 1 } }); // => 1
topX({ y: 1 }); // => undefined
topX(null); // => null
@param path {string}
`.` delimited path to a field
@param target {object}
Target to get a field by the given `name` from
@returns {object|function|string|number|boolean}
Field value
</api>
<api name="isInstance">
@function
Takes `Type` (constructor function) and a `value` and returns
`true` if `value` is instance of the given `Type`. Function is
implicitly curried this allows predicate generation by calling
function with just first argument.
const { isInstance } = require("sdk/lang/functional");
function X() {}
function Y() {}
let isX = isInstance(X);
isInstance(X, new X); // true
isInstance(X)(new X); // true
isInstance(X, new Y); // false
isInstance(X)(new Y); // false
isX(new X); // true
isX(new Y); // false
@param Type {function}
Type (constructor function)
@param instance {object}
Instance to test
@returns {boolean}
</api>
<api name="is">
@function
Functions takes `expected` and `actual` values and returns `true` if
`expected === actual`. If invoked with just one argument returns pratially
applied function, which can be invoked to provide a second argument, this
is handy with `map`, `filter` and other high order functions:
const { is } = require("sdk/util/oops");
[ 1, 0, 1, 0, 1 ].map(is(1)) // => [ true, false, true, false, true ]
@param expected {object|string|number|boolean}
@param actual {object|string|number|boolean}
@returns {boolean}
</api>
<api name="isnt">
@function
Functions takes `expected` and `actual` values and returns `true` if
`expected !== actual`. If invoked with just one argument returns pratially
applied function, which can be invoked with a second argument, which is
handy with `map`, `filter` and other high order functions:
const { isnt } = require("sdk/util/oops");
[ 1, 0, 1, 0, 1 ].map(isnt(0)) // => [ true, false, true, false, true ]
@param expected {object|string|number|boolean}
@param actual {object|string|number|boolean}
@returns {boolean}
</api>

View File

@ -9,7 +9,7 @@ module.metadata = {
};
const { on, once, off, setListeners } = require('./core');
const { method, chain } = require('../lang/functional');
const { method, chainable } = require('../lang/functional');
const { Class } = require('../core/heritage');
/**
@ -43,7 +43,7 @@ const EventTarget = Class({
* console.log('data received: ' + data)
* })
*/
on: chain(method(on)),
on: chainable(method(on)),
/**
* Registers an event `listener` that is called once the next time an event
* of the specified `type` is emitted.
@ -52,7 +52,7 @@ const EventTarget = Class({
* @param {Function} listener
* The listener function that processes the event.
*/
once: chain(method(once)),
once: chainable(method(once)),
/**
* Removes an event `listener` for the given event `type`.
* @param {String} type

View File

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Disclaimer: Most of the functions in this module implement APIs from
// Disclaimer: Some of the functions in this module implement APIs from
// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
// those goes to him.
@ -12,19 +12,33 @@ module.metadata = {
"stability": "unstable"
};
const { setImmediate, setTimeout } = require("../timers");
const { deprecateFunction } = require("../util/deprecate");
const { setImmediate, setTimeout } = require("../timers");
const arity = f => f.arity || f.length;
const name = f => f.displayName || f.name;
const derive = (f, source) => {
f.displayName = name(source);
f.arity = arity(source);
return f;
};
/**
* Takes `lambda` function and returns a method. When returned method is
* invoked it calls wrapped `lambda` and passes `this` as a first argument
* and given argument as rest.
* Takes variadic numeber of functions and returns composed one.
* Returned function pushes `this` pseudo-variable to the head
* of the passed arguments and invokes all the functions from
* left to right passing same arguments to them. Composite function
* returns return value of the right most funciton.
*/
function method(lambda) {
return function method() {
return lambda.apply(null, [this].concat(Array.slice(arguments)));
}
}
const method = (...lambdas) => {
return function method(...args) {
args.unshift(this);
return lambdas.reduce((_, lambda) => lambda.apply(this, args),
void(0));
};
};
exports.method = method;
/**
@ -34,24 +48,13 @@ exports.method = method;
* function is reused, instead of creating a new one each time. This also allows
* to use this functions as event listeners.
*/
function defer(f) {
return function deferred() setImmediate(invoke, f, arguments, this);
}
const defer = f => derive(function(...args) {
setImmediate(invoke, f, args, this);
}, f);
exports.defer = defer;
// Exporting `remit` alias as `defer` may conflict with promises.
exports.remit = defer;
/*
* Takes a funtion and returns a wrapped function that returns `this`
*/
function chain(f) {
return function chainable(...args) {
f.apply(this, args);
return this;
};
}
exports.chain = chain;
/**
* Invokes `callee` by passing `params` as an arguments and `self` as `this`
* pseudo-variable. Returns value that is returned by a callee.
@ -62,7 +65,7 @@ exports.chain = chain;
* @param {Object} self
* Object to be passed as a `this` pseudo variable.
*/
function invoke(callee, params, self) callee.apply(self, params);
const invoke = (callee, params, self) => callee.apply(self, params);
exports.invoke = invoke;
/**
@ -74,14 +77,16 @@ exports.invoke = invoke;
*
* @returns The new function with binded values
*/
function partial(fn) {
if (typeof fn !== "function")
throw new TypeError(String(fn) + " is not a function");
const partial = (f, ...curried) => {
if (typeof(f) !== "function")
throw new TypeError(String(f) + " is not a function");
let args = Array.slice(arguments, 1);
return function() fn.apply(this, args.concat(Array.slice(arguments)));
}
let fn = derive(function(...args) {
return f.apply(this, curried.concat(args));
}, f);
fn.arity = arity(f) - curried.length;
return fn;
};
exports.partial = partial;
/**
@ -98,12 +103,11 @@ exports.partial = partial;
* console.log(sum(2, 2)) // 4
* console.log(sum(2)(4)) // 6
*/
var curry = new function() {
function currier(fn, arity, params) {
const curry = new function() {
const currier = (fn, arity, params) => {
// Function either continues to curry arguments or executes function
// if desired arguments have being collected.
return function curried() {
var input = Array.slice(arguments);
const curried = function(...input) {
// Prepend all curried arguments to the given arguments.
if (params) input.unshift.apply(input, params);
// If expected number of arguments has being collected invoke fn,
@ -111,11 +115,12 @@ var curry = new function() {
return (input.length >= arity) ? fn.apply(this, input) :
currier(fn, arity, input);
};
}
curried.arity = arity - (params ? params.length : 0);
return function curry(fn) {
return currier(fn, fn.length);
}
return curried;
};
return fn => currier(fn, arity(fn));
};
exports.curry = curry;
@ -131,12 +136,12 @@ exports.curry = curry;
*
* welcome('moe'); // => 'hi: moe!'
*/
function compose() {
let lambdas = Array.slice(arguments);
return function composed() {
let args = Array.slice(arguments), index = lambdas.length;
function compose(...lambdas) {
return function composed(...args) {
let index = lambdas.length;
while (0 <= --index)
args = [ lambdas[index].apply(this, args) ];
args = [lambdas[index].apply(this, args)];
return args[0];
};
}
@ -155,16 +160,15 @@ exports.compose = compose;
*
* hello(); // => 'before, hello: moe, after'
*/
function wrap(f, wrapper) {
return function wrapped()
wrapper.apply(this, [ f ].concat(Array.slice(arguments)))
};
const wrap = (f, wrapper) => derive(function wrapped(...args) {
return wrapper.apply(this, [f].concat(args));
}, f);
exports.wrap = wrap;
/**
* Returns the same value that is used as the argument. In math: f(x) = x
*/
function identity(value) value
const identity = value => value;
exports.identity = identity;
/**
@ -174,14 +178,25 @@ exports.identity = identity;
* the arguments to the original function. The default hashFunction just uses
* the first argument to the memoized function as the key.
*/
function memoize(f, hasher) {
const memoize = (f, hasher) => {
let memo = Object.create(null);
let cache = new WeakMap();
hasher = hasher || identity;
return function memoizer() {
let key = hasher.apply(this, arguments);
return key in memo ? memo[key] : (memo[key] = f.apply(this, arguments));
};
}
return derive(function memoizer(...args) {
const key = hasher.apply(this, args);
const type = typeof(key);
if (key && (type === "object" || type === "function")) {
if (!cache.has(key))
cache.set(key, f.apply(this, args));
return cache.get(key);
}
else {
if (!(key in memo))
memo[key] = f.apply(this, args);
return memo[key];
}
}, f);
};
exports.memoize = memoize;
/**
@ -189,9 +204,8 @@ exports.memoize = memoize;
* the optional arguments, they will be forwarded on to the function when it is
* invoked.
*/
function delay(f, ms) {
let args = Array.slice(arguments, 2);
setTimeout(function(context) { return f.apply(context, args); }, ms, this);
const delay = function delay(f, ms, ...args) {
setTimeout(() => f.apply(this, args), ms);
};
exports.delay = delay;
@ -201,10 +215,116 @@ exports.delay = delay;
* the original call. Useful for initialization functions, instead of having to
* set a boolean flag and then check it later.
*/
function once(f) {
const once = f => {
let ran = false, cache;
return function() ran ? cache : (ran = true, cache = f.apply(this, arguments))
return derive(function(...args) {
return ran ? cache : (ran = true, cache = f.apply(this, args));
}, f);
};
exports.once = once;
// export cache as once will may be conflicting with event once a lot.
exports.cache = once;
// Takes a `f` function and returns a function that takes the same
// arguments as `f`, has the same effects, if any, and returns the
// opposite truth value.
const complement = f => derive(function(...args) {
return args.length < arity(f) ? complement(partial(f, ...args)) :
!f.apply(this, args);
}, f);
exports.complement = complement;
// Constructs function that returns `x` no matter what is it
// invoked with.
const constant = x => _ => x;
exports.constant = constant;
// Takes `p` predicate, `consequent` function and an optional
// `alternate` function and composes function that returns
// application of arguments over `consequent` if application over
// `p` is `true` otherwise returns application over `alternate`.
// If `alternate` is not a function returns `undefined`.
const when = (p, consequent, alternate) => {
if (typeof(alternate) !== "function" && alternate !== void(0))
throw TypeError("alternate must be a function");
if (typeof(consequent) !== "function")
throw TypeError("consequent must be a function");
return function(...args) {
return p.apply(this, args) ?
consequent.apply(this, args) :
alternate && alternate.apply(this, args);
};
};
exports.when = when;
// Apply function that behaves as `apply` does in lisp:
// apply(f, x, [y, z]) => f.apply(f, [x, y, z])
// apply(f, x) => f.apply(f, [x])
const apply = (f, ...rest) => f.apply(f, rest.concat(rest.pop()));
exports.apply = apply;
// Returns function identical to given `f` but with flipped order
// of arguments.
const flip = f => derive(function(...args) {
return f.apply(this, args.reverse());
}, f);
exports.flip = flip;
// Takes field `name` and `target` and returns value of that field.
// If `target` is `null` or `undefined` it would be returned back
// instead of attempt to access it's field. Function is implicitly
// curried, this allows accessor function generation by calling it
// with only `name` argument.
const field = curry((name, target) =>
// Note: Permisive `==` is intentional.
target == null ? target : target[name]);
exports.field = field;
// Takes `.` delimited string representing `path` to a nested field
// and a `target` to get it from. For convinience function is
// implicitly curried, there for accessors can be created by invoking
// it with just a `path` argument.
const query = curry((path, target) => {
const names = path.split(".");
const count = names.length;
let index = 0;
let result = target;
// Note: Permisive `!=` is intentional.
while (result != null && index < count) {
result = result[names[index]];
index = index + 1;
}
return result;
});
exports.query = query;
// Takes `Type` (constructor function) and a `value` and returns
// `true` if `value` is instance of the given `Type`. Function is
// implicitly curried this allows predicate generation by calling
// function with just first argument.
const isInstance = curry((Type, value) => value instanceof Type);
exports.isInstance = isInstance;
/*
* Takes a funtion and returns a wrapped function that returns `this`
*/
const chainable = f => derive(function(...args) {
f.apply(this, args);
return this;
}, f);
exports.chainable = chainable;
exports.chain =
deprecateFunction(chainable, "Function `chain` was renamed to `chainable`");
// Functions takes `expected` and `actual` values and returns `true` if
// `expected === actual`. Returns curried function if called with less then
// two arguments.
//
// [ 1, 0, 1, 0, 1 ].map(is(1)) // => [ true, false, true, false, true ]
const is = curry((expected, actual) => actual === expected);
exports.is = is;
const isnt = complement(is);
exports.isnt = isnt;

View File

@ -1,21 +1,22 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { setTimeout } = require('sdk/timers');
const utils = require('sdk/lang/functional');
const { invoke, defer, partial, compose, memoize, once, delay, wrap, curry, chain } = utils;
const { invoke, defer, partial, compose, memoize, once, is, isnt,
delay, wrap, curry, chainable, field, query, isInstance } = utils;
const { LoaderWithHookedConsole } = require('sdk/test/loader');
exports['test forwardApply'] = function(assert) {
function sum(b, c) this.a + b + c
function sum(b, c) { return this.a + b + c; }
assert.equal(invoke(sum, [2, 3], { a: 1 }), 6,
'passed arguments and pseoude-variable are used');
assert.equal(invoke(sum.bind({ a: 2 }), [2, 3], { a: 1 }), 7,
'bounded `this` pseoudo variable is used');
}
};
exports['test deferred function'] = function(assert, done) {
let nextTurn = false;
@ -26,13 +27,13 @@ exports['test deferred function'] = function(assert, done) {
done();
}
let fixture = { a: 1, method: defer(sum) }
let fixture = { a: 1, method: defer(sum) };
fixture.method(2, 3);
nextTurn = true;
};
exports['test partial function'] = function(assert) {
function sum(b, c) this.a + b + c;
function sum(b, c) { return this.a + b + c; }
let foo = { a : 5 };
@ -68,11 +69,11 @@ exports['test compose'] = function(assert) {
let target = {
name: 'Joe',
greet: compose(function exclaim(sentence) {
return sentence + '!'
return sentence + '!';
}, function(title) {
return 'hi : ' + title + ' ' + this.name;
})
}
};
assert.equal(target.greet('Mr'), 'hi : Mr Joe!',
'this can be passed in');
@ -106,7 +107,7 @@ exports['test wrap'] = function(assert) {
assert.equal(target.hi(), 'Hello Matteo', 'works with this');
function noop() { };
function noop() { }
let wrapped = wrap(noop, function(f) {
return Array.slice(arguments);
});
@ -117,7 +118,7 @@ exports['test wrap'] = function(assert) {
};
exports['test memoize'] = function(assert) {
function fib(n) n < 2 ? n : fib(n - 1) + fib(n - 2)
const fib = n => n < 2 ? n : fib(n - 1) + fib(n - 2);
let fibnitro = memoize(fib);
assert.equal(fib(10), 55,
@ -125,7 +126,7 @@ exports['test memoize'] = function(assert) {
assert.equal(fibnitro(10), 55,
'a memoized version of fibonacci produces identical results');
function o(key, value) { return value; };
function o(key, value) { return value; }
let oo = memoize(o), v1 = {}, v2 = {};
@ -136,12 +137,12 @@ exports['test memoize'] = function(assert) {
assert.notEqual(oo(1), oo(2), 'values do not override');
assert.equal(o(3, v2), oo(2, 3), 'returns same value as un-memoized');
let get = memoize(function(attribute) this[attribute])
let target = { name: 'Bob', get: get }
let get = memoize(function(attribute) { return this[attribute]; });
let target = { name: 'Bob', get: get };
assert.equal(target.get('name'), 'Bob', 'has correct `this`');
assert.equal(target.get.call({ name: 'Jack' }, 'name'), 'Bob',
'name is memoized')
'name is memoized');
assert.equal(get('name'), 'Bob', 'once memoized can be called without this');
};
@ -155,13 +156,13 @@ exports['test delay'] = function(assert, done) {
};
exports['test delay with this'] = function(assert, done) {
let context = {}
let context = {};
delay.call(context, function(name) {
assert.equal(this, context, 'this was passed in');
assert.equal(name, 'Tom', 'argument was passed in');
done();
}, 10, 'Tom');
}
};
exports['test once'] = function(assert) {
let n = 0;
@ -172,7 +173,12 @@ exports['test once'] = function(assert) {
assert.equal(n, 1, 'only incremented once');
let target = { state: 0, update: once(function() this.state ++ ) };
let target = {
state: 0,
update: once(function() {
return this.state ++;
})
};
target.update();
target.update();
@ -182,7 +188,7 @@ exports['test once'] = function(assert) {
exports['test once with argument'] = function(assert) {
let n = 0;
let increment = once(function(a) n++);
let increment = once(a => n++);
increment();
increment('foo');
@ -195,11 +201,121 @@ exports['test once with argument'] = function(assert) {
assert.equal(n, 1, 'only incremented once');
};
exports['test chain'] = function (assert) {
exports['test complement'] = assert => {
let { complement } = require("sdk/lang/functional");
let isOdd = x => Boolean(x % 2);
assert.equal(isOdd(1), true);
assert.equal(isOdd(2), false);
let isEven = complement(isOdd);
assert.equal(isEven(1), false);
assert.equal(isEven(2), true);
let foo = {};
let isFoo = function() { return this === foo; };
let insntFoo = complement(isFoo);
assert.equal(insntFoo.call(foo), false);
assert.equal(insntFoo.call({}), true);
};
exports['test constant'] = assert => {
let { constant } = require("sdk/lang/functional");
let one = constant(1);
assert.equal(one(1), 1);
assert.equal(one(2), 1);
};
exports['test apply'] = assert => {
let { apply } = require("sdk/lang/functional");
let dashify = (...args) => args.join("-");
assert.equal(apply(dashify, 1, [2, 3]), "1-2-3");
assert.equal(apply(dashify, "a"), "a");
assert.equal(apply(dashify, ["a", "b"]), "a-b");
assert.equal(apply(dashify, ["a", "b"], "c"), "a,b-c");
assert.equal(apply(dashify, [1, 2], [3, 4]), "1,2-3-4");
};
exports['test flip'] = assert => {
let { flip } = require("sdk/lang/functional");
let append = (left, right) => left + " " + right;
let prepend = flip(append);
assert.equal(append("hello", "world"), "hello world");
assert.equal(prepend("hello", "world"), "world hello");
let wrap = function(left, right) {
return left + " " + this + " " + right;
};
let invertWrap = flip(wrap);
assert.equal(wrap.call("@", "hello", "world"), "hello @ world");
assert.equal(invertWrap.call("@", "hello", "world"), "world @ hello");
let reverse = flip((...args) => args);
assert.deepEqual(reverse(1, 2, 3, 4), [4, 3, 2, 1]);
assert.deepEqual(reverse(1), [1]);
assert.deepEqual(reverse(), []);
// currying still works
let prependr = curry(prepend);
assert.equal(prependr("hello", "world"), "world hello");
assert.equal(prependr("hello")("world"), "world hello");
};
exports["test when"] = assert => {
let { when } = require("sdk/lang/functional");
let areNums = (...xs) => xs.every(x => typeof(x) === "number");
let sum = when(areNums, (...xs) => xs.reduce((y, x) => x + y, 0));
assert.equal(sum(1, 2, 3), 6);
assert.equal(sum(1, 2, "3"), undefined);
let multiply = when(areNums,
(...xs) => xs.reduce((y, x) => x * y, 1),
(...xs) => xs);
assert.equal(multiply(2), 2);
assert.equal(multiply(2, 3), 6);
assert.deepEqual(multiply(2, "4"), [2, "4"]);
function Point(x, y) {
this.x = x;
this.y = y;
}
let isPoint = x => x instanceof Point;
let inc = when(isPoint, ({x, y}) => new Point(x + 1, y + 1));
assert.equal(inc({}), undefined);
assert.deepEqual(inc(new Point(0, 0)), { x: 1, y: 1 });
let axis = when(isPoint,
({ x, y }) => [x, y],
_ => [0, 0]);
assert.deepEqual(axis(new Point(1, 4)), [1, 4]);
assert.deepEqual(axis({ foo: "bar" }), [0, 0]);
};
exports["test chainable"] = function(assert) {
let Player = function () { this.volume = 5; };
Player.prototype = {
setBand: chain(function (band) this.band = band),
incVolume: chain(function () this.volume++)
setBand: chainable(function (band) { return (this.band = band); }),
incVolume: chainable(function () { return this.volume++; })
};
let player = new Player();
player
@ -210,4 +326,97 @@ exports['test chain'] = function (assert) {
assert.equal(player.volume, 11, 'accepts no arguments in chain');
};
exports["test field"] = assert => {
let Num = field("constructor", 0);
assert.equal(Num.name, Number.name);
assert.ok(typeof(Num), "function");
let x = field("x");
[
[field("foo", { foo: 1 }), 1],
[field("foo")({ foo: 1 }), 1],
[field("bar", {}), undefined],
[field("bar")({}), undefined],
[field("hey", undefined), undefined],
[field("hey")(undefined), undefined],
[field("how", null), null],
[field("how")(null), null],
[x(1), undefined],
[x(undefined), undefined],
[x(null), null],
[x({ x: 1 }), 1],
[x({ x: 2 }), 2],
].forEach(([actual, expected]) => assert.equal(actual, expected));
};
exports["test query"] = assert => {
let Num = query("constructor", 0);
assert.equal(Num.name, Number.name);
assert.ok(typeof(Num), "function");
let x = query("x");
let xy = query("x.y");
[
[query("foo", { foo: 1 }), 1],
[query("foo")({ foo: 1 }), 1],
[query("foo.bar", { foo: { bar: 2 } }), 2],
[query("foo.bar")({ foo: { bar: 2 } }), 2],
[query("foo.bar", { foo: 1 }), undefined],
[query("foo.bar")({ foo: 1 }), undefined],
[x(1), undefined],
[x(undefined), undefined],
[x(null), null],
[x({ x: 1 }), 1],
[x({ x: 2 }), 2],
[xy(1), undefined],
[xy(undefined), undefined],
[xy(null), null],
[xy({ x: 1 }), undefined],
[xy({ x: 2 }), undefined],
[xy({ x: { y: 1 } }), 1],
[xy({ x: { y: 2 } }), 2]
].forEach(([actual, expected]) => assert.equal(actual, expected));
};
exports["test isInstance"] = assert => {
function X() {}
function Y() {}
let isX = isInstance(X);
[
isInstance(X, new X()),
isInstance(X)(new X()),
!isInstance(X, new Y()),
!isInstance(X)(new Y()),
isX(new X()),
!isX(new Y())
].forEach(x => assert.ok(x));
};
exports["test is"] = assert => {
assert.deepEqual([ 1, 0, 1, 0, 1 ].map(is(1)),
[ true, false, true, false, true ],
"is can be partially applied");
assert.ok(is(1, 1));
assert.ok(!is({}, {}));
assert.ok(is()(1)()(1), "is is curried");
assert.ok(!is()(1)()(2));
};
exports["test isnt"] = assert => {
assert.deepEqual([ 1, 0, 1, 0, 1 ].map(isnt(0)),
[ true, false, true, false, true ],
"is can be partially applied");
assert.ok(!isnt(1, 1));
assert.ok(isnt({}, {}));
assert.ok(!isnt()(1)()(1));
assert.ok(isnt()(1)()(2));
};
require('test').run(exports);