Permalink
Browse files

promise version

  • Loading branch information...
1 parent 36b5c88 commit d352109b2a900fd314b9919ff6f8c586e8d1f254 @jsdnxx committed Feb 12, 2013
Showing with 290 additions and 190 deletions.
  1. +46 −44 README.md
  2. +73 −21 index.js
  3. +12 −10 package.json
  4. +159 −115 test/test.connective.js
View
@@ -1,76 +1,78 @@
-# connective
-combine predicate (bool returning) functions with propositional logic connectives (and, or, not)
+# connective-promise
+boolean (true/false) and first order (some/every) logic with promises
## installation
- $ npm install connective
+ $ npm install connective-promise
## usage
```js
-var connective = require('connective')
-var or = connective.or
-var and = connective.and
-var not = connective.not
-
-function wearsFlannel (person) {
- return person.wearing === 'flannel'
+var connectiveP = require('connective-promise')
+var or = connectiveP.or
+var and = connectiveP.and
+var not = connectiveP.not
+var every = connectiveP.every
+var some = connectiveP.some
+var Q = require('your favorite promises implementation')
+
+function userIsAuthenticated () {
+ return Q.resolve(true)
}
-function ridesBikes (person) {
- return person.rides === 'bikes'
-}
+weGetSignal = Q.resolve(true)
-var isSquare = not(or(wearsFlannel, ridesBikes))
-var isHipster = and(wearsFlannel, ridesBikes)
-var isLumberjack = and(wearsFlannel, not(ridesBikes))
+weAreReady = false
-var people = {
- jon: { wearing: 'flannel', rides: 'nothing'}
- kurt: { wearing: 'flannel', rides: 'bikes'}
- bob: { wearing: 'hoodie', rides: 'scooters'}
-}
+connectiveP.or(weAreReady, weGetSignal, userIsAuthenticated, weGetSignal)
+// => Promise<true>,
+// terms evaluated in serial, so userIsAuthenticated not called
-for(var name in people) {
- var person = people[name]
- console.log(name, isSquare(person), isHipster(person), isLumberjack(person))
-}
-```
-## about
-In propositional logic, boolean statements are joined together by connectives. Logicians would call them conjunctions, disjunctions, and negations, but programmers know them as `&&`, `||`, and `!`. The problem with using these language-level connective operators is that they apply at evaluation time, and thus aren't very composable.
+connectiveP.some(weAreReady, userIsAuthenticated, weGetSignal)
+// => Promise<true>,
+// terms evaluated in parallel, so userIsAuthenticated is called
-Functions which take a value and return a boolean are known as predicates. They are useful, for example, in conditional branching, validation, and business rules.
-The functions in `connective` let you compose predicates into composite expressions which can be used as functions and evaluated later against other data.
-## api
+connectiveP.all(weAreReady, userIsAuthenticated, weGetSignal)
+// => Promise<false>,
+// terms evaluated in serial so userIsAuthenticated is not called
-In describing function signatures below, `Predicate` is a function which takes any number of arguments and returns a `boolean`: `function(...) => boolean`
-### `connective.or: function (term1 : Predicate, ..., termN : Predicate) => Predicate`
+connectiveP.every(weAreReady, userIsAuthenticated, weGetSignal))
+// => Promise<false>,
+// terms evaluated in parallel, so userIsAuthenticated is not called
+
+
+connectiveP.not(userIsAuthenticated)
+// => Promise<false>
+
+```
+
+## about
-Returns a Predicate combining one or more Predicate terms with a logical `or` (disjunction), roughly equivalent to writing
+`connective-promise` helps you compose boolean values, boolean promises, and continuations (functions without parameters) returning booleans or boolean promises.
- function (x) { return Predicate1(x) || Predicate2(x) }
+In synchronous code, it can be useful to depend on operator precedence for control flow, for example, to evaluate a function depending on a certain value:
-The returned Predicate will pass through its `this` context and arguments to each of the Predicate terms which are necessary to evaluate the expression.
+ var showPage = (userIsAuthenticated() && userIsAuthorized()) || todayIsTuesday;
-### `connective.and: function (term1 : Predicate, ... termN : Predicate) => Predicate`
+In this case, we would only have to check for authorization if the user passes the first condition. This kind of logic is very clear in synchronous code, but can be obscured in callbacks in async code.
-Returns a Predicate combining one or more Predicate terms with a logical `and` (conjunction), roughly equivalent to writing
+Now, we could have those be promise-returning functions and write:
- function (x) { return Predicate1(x) && Predicate2(x) }
+ var showPage = connectiveP.or(todayIsTuesday, connectiveP.and(userIsAuthenticated, userIsAuthorized))
-The returned Predicate will pass through its `this` context and arguments to each of the Predicate terms.
+where userIsAuthenticated is a function which returns a promise. Here, if the term is necessary to evaluate the overall truth of the statement, `connective-promise` will invoke the function and await the promised value.
-### `connective.not: function (term : Predicate) => Predicate`
+In cases where there are no side effects and latency is more important than economy of computation, you can evaluate the terms in parallel.
-Returns a Predicate negating `term`, roughly equivalent to writing
+And of course, you have first call support for error propagation through `promise.reject`.
- function (x) { return !Predicate(x) }
+## see also
-The returned Predicate will pass through its `this` context and arguments to `term`
+[connective](https://npmjs.org/package/connective) offers similar composable semantics for synchronous predicate functions.
## running the tests
View
@@ -1,35 +1,87 @@
-var each = Array.prototype.forEach
-var every = Array.prototype.every
-var some = Array.prototype.some
+var Q = require('q')
+var slice = Array.prototype.slice
+
+function isPromise(p) {
+ return p && typeof p.then === 'function'
+}
+
+function pcall (p) {
+ var t = typeof p
+ if (t === 'boolean') return Q.resolve(p)
+ if (t === 'function') return pcall(p())
+ if (isPromise(p)) return p
+ return Q.resolve(Boolean(p))
+}
function or () {
- var terms = arguments
- return function () {
- var ctx = this;
- var args = arguments;
- return some.call(terms, function (term) {
- return !!term.apply(ctx, args)
- })
+ var args = arguments;
+ var term = args[0]
+ if (!term) return Q.reject(new Error('No terms'));
+ if (args.length === 1) {
+ return pcall(term).then(Boolean)
}
+
+ return pcall(term).then(function (val) {
+ if (val) return true;
+ // async recurse
+ return or.apply(null, slice.call(args, 1))
+ })
}
function and () {
- var terms = arguments
- return function () {
- var ctx = this;
- var args = arguments;
- return every.call(terms, function (term) {
- return !!term.apply(ctx, args)
- })
+ var args = arguments;
+ var term = args[0]
+ if (!term) return Q.reject(new Error('No terms'))
+ if (args.length === 1) {
+ return pcall(term).then(Boolean)
}
+
+ return pcall(term).then(function (val) {
+ if (!val) return false;
+ // asycn recurse
+ return and.apply(null, slice.call(args, 1))
+ })
}
-function not (term) {
- return function () {
- return !term.apply(this, arguments)
+
+function some () {
+ var args = arguments;
+ var term = args[0]
+ if (!term) return Q.reject(new Error('No terms'))
+ if (args.length === 1) {
+ return pcall(term).then(Boolean)
}
+
+ var promises = slice.call(arguments).map(pcall)
+
+ return Q.all(promises).then(function (results) {
+ return results.some(Boolean)
+ })
+}
+
+function every () {
+ var args = arguments;
+ var term = args[0]
+ if (!term) return Q.reject(new Error('No terms'))
+ if (args.length === 1) {
+ return pcall(term).then(Boolean)
+ }
+
+ var promises = slice.call(arguments).map(pcall)
+
+ return Q.all(promises).then(function (results) {
+ return results.every(Boolean)
+ })
+}
+
+
+function not(term) {
+ if (!term) return Q.reject(new Error('No term'))
+ return pcall(term).then(function (x) { return !x })
}
module.exports.or = or
module.exports.and = and
-module.exports.not = not
+module.exports.not = not
+module.exports.every= every
+module.exports.some = some
View
@@ -1,14 +1,12 @@
{
- "name": "connective",
- "version": "1.0.0",
- "description": "combine predicate (bool returning) functions with propositional logic connectives (and, or, not)",
+ "name": "connective-promise",
+ "version": "0.5.0",
+ "description": "boolean (true/false) and first order (some/every) logic with promises",
"keywords": [
- "functional",
- "predicate",
- "higher order functions",
"boolean",
- "propositional",
- "logic"
+ "first order logic",
+ "promises",
+ "async"
],
"main": "index.js",
"directories": {
@@ -22,13 +20,17 @@
"url": "git@github.com:AgileDiagnosis/node-connective.git"
},
"author": "Agile Diagnosis <hello@agilediagnosis.com",
- "contributors": ["jden <jason@denizac.org>"],
+ "contributors": [
+ "jden <jason@denizac.org>"
+ ],
"license": "MIT",
"devDependencies": {
"sinon-chai": "~2.3.1",
"chai-interface": "~1.0.1",
"chai": "~1.5.0",
"mocha": "~1.8.1",
- "sinon": "~1.5.2"
+ "sinon": "~1.5.2",
+ "q": "~0.8.12",
+ "ski": "~1.0.0"
}
}
Oops, something went wrong.

0 comments on commit d352109

Please sign in to comment.