Permalink
Browse files

open

  • Loading branch information...
0 parents commit 6d039be7d3af2f5761419b224dbd87bf3fe43e18 @jsdnxx committed Feb 8, 2013
Showing with 336 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +7 −0 LICENSE.md
  3. +85 −0 README.md
  4. +35 −0 index.js
  5. +26 −0 package.json
  6. +182 −0 test/test.connective.js
1 .gitignore
@@ -0,0 +1 @@
+node_modules
7 LICENSE.md
@@ -0,0 +1,7 @@
+Copyright © 2013 Agile Diagnosis, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
85 README.md
@@ -0,0 +1,85 @@
+# connective
+combine predicate (bool returning) functions with propositional logic connectives (and, or, not)
+
+## installation
+
+ $ npm install connective
+
+## usage
+```js
+var connective = require('connective')
+var or = connective.or
+var and = connective.and
+var not = connective.and
+
+function wearsFlannel (person) {
+ return person.wearing === 'flannel'
+}
+
+function ridesBikes (person) {
+ return person.rides === 'bikes'
+}
+
+var isSquare = not(or(wearsFlannel, ridesBikes))
+var isHipster = and(wearsFlannel, ridesBikes)
+var isLumberjack = and(wearsFlannel, not(ridesBikes))
+
+var people = {
+ jon: { wearing: 'flannel', rides: 'nothing'}
+ kurt: { wearing: 'flannel', rides: 'bikes'}
+ bob: { wearing: 'hoodie', rides: 'scooters'}
+}
+
+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.
+
+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
+
+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`
+
+Returns a Predicate combining one or more Predicate terms with a logical `or` (disjunction), roughly equivalent to writing
+
+ function (x) { return Predicate1(x) || Predicate2(x) }
+
+The returned Predicate will pass through its `this` context and arguments to each of the Predicate terms which are necessary to evaluate the expression.
+
+### `connective.and: function (term1 : Predicate, ... termN : Predicate) => Predicate`
+
+Returns a Predicate combining one or more Predicate terms with a logical `and` (conjunction), roughly equivalent to writing
+
+ function (x) { return Predicate1(x) && Predicate2(x) }
+
+The returned Predicate will pass through its `this` context and arguments to each of the Predicate terms.
+
+### `connective.not: function (term : Predicate) => Predicate`
+
+Returns a Predicate negating `term`, roughly equivalent to writing
+
+ function (x) { return !Predicate(x) }
+
+The returned Predicate will pass through its `this` context and arguments to `term`
+
+## running the tests
+
+ $ npm install
+ $ npm test
+
+## contributors
+
+jden <jason@denizac.org>
+
+## license
+MIT. (c) 2013 Agile Diagnosis. See LICENSE.md
35 index.js
@@ -0,0 +1,35 @@
+var each = Array.prototype.forEach
+var every = Array.prototype.every
+var some = Array.prototype.some
+
+function or () {
+ var terms = arguments
+ return function () {
+ var ctx = this;
+ var args = arguments;
+ return some.call(terms, function (term) {
+ return !!term.apply(ctx, args)
+ })
+ }
+}
+
+function and () {
+ var terms = arguments
+ return function () {
+ var ctx = this;
+ var args = arguments;
+ return every.call(terms, function (term) {
+ return !!term.apply(ctx, args)
+ })
+ }
+}
+
+function not (term) {
+ return function () {
+ return !term.apply(this, arguments)
+ }
+}
+
+module.exports.or = or
+module.exports.and = and
+module.exports.not = not
26 package.json
@@ -0,0 +1,26 @@
+{
+ "name": "connective",
+ "version": "1.0.0",
+ "description": "combine predicate (bool returning) functions with propositional logic connectives (and, or, not)",
+ "main": "index.js",
+ "directories": {
+ "test": "test"
+ },
+ "scripts": {
+ "test": "node node_modules/mocha/bin/mocha"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git@github.com:AgileDiagnosis/node-connective.git"
+ },
+ "author": "Agile Diagnosis <hello@agilediagnosis.com",
+ "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"
+ }
+}
182 test/test.connective.js
@@ -0,0 +1,182 @@
+var chai = require('chai')
+chai.should()
+chai.use(require('chai-interface'))
+var sinon = require('sinon')
+chai.use(require('sinon-chai'))
+
+describe('connective', function () {
+ var connective = require('../index')
+
+ var True = function () { return true }
+ var False = function () { return false }
+
+ var Truthy = function () { return 1 }
+ var Falsy = function () { return 0}
+
+ it('has interface', function () {
+ connective.should.have.interface({
+ or: Function,
+ and: Function,
+ not: Function
+ })
+ })
+
+ describe('not', function () {
+ var not = connective.not
+
+ it('returns an inverted predicate', function () {
+ var f = not(True)
+ f.should.be.a('function')
+ f().should.equal(false)
+
+ var t = not(False)
+ t.should.be.a('function')
+ t().should.equal(true)
+
+ var fy = not(Truthy)
+ fy.should.be.a('function')
+ fy().should.equal(false)
+
+ var ty = not(Falsy)
+ ty.should.be.a('function')
+ ty().should.equal(true)
+ })
+
+ it('passes context through to term', function () {
+ var term = sinon.stub().returns(true)
+ var f = not(term)
+ var context = {}
+
+ f.call(context)
+ term.should.have.been.calledOn(context)
+ })
+
+ it('passes arguments through to term', function () {
+ var term = sinon.stub().returns(true)
+ var f = not(term)
+
+ f(1, 2, 3)
+ term.should.have.been.calledWithExactly(1, 2, 3)
+ })
+
+ })
+
+ describe('and', function () {
+ var and = connective.and
+
+ it('returns a conjunctive predicate', function () {
+ var t = and(True)
+ t.should.be.a('function')
+ t().should.equal(true)
+
+ var t = and(True, True)
+ t.should.be.a('function')
+ t().should.equal(true)
+
+ var t = and(True, Truthy)
+ t.should.be.a('function')
+ t().should.equal(true)
+
+ var f = and(True, False)
+ f.should.be.a('function')
+ f().should.equal(false)
+
+ var f = and(False, True)
+ f.should.be.a('function')
+ f().should.equal(false)
+
+ var f = and(Falsy, True)
+ f.should.be.a('function')
+ f().should.equal(false)
+
+ })
+
+ it('passes context through to term', function () {
+ var term1 = sinon.stub().returns(true)
+ var term2 = sinon.stub().returns(true)
+ var t = and(term1, term2)
+ var context = {}
+
+ t.call(context)
+ term1.should.have.been.calledOn(context)
+ term2.should.have.been.calledOn(context)
+ })
+
+ it('passes arguments through to term', function () {
+ var term1 = sinon.stub().returns(true)
+ var term2 = sinon.stub().returns(true)
+ var t = and(term1, term2)
+
+ t(1, 2, 3)
+ term1.should.have.been.calledWithExactly(1, 2, 3)
+ term2.should.have.been.calledWithExactly(1, 2, 3)
+ })
+
+ })
+
+ describe('or', function () {
+ var or = connective.or
+
+ it('returns a disjunctive predicate', function () {
+ var t = or(True)
+ t.should.be.a('function')
+ t().should.equal(true)
+
+ var t = or(True, True)
+ t.should.be.a('function')
+ t().should.equal(true)
+
+ var t = or(True, Truthy)
+ t.should.be.a('function')
+ t().should.equal(true)
+
+ var t = or(True, False)
+ t.should.be.a('function')
+ t().should.equal(true)
+
+ var t = or(False, True)
+ t.should.be.a('function')
+ t().should.equal(true)
+
+ var t = or(Falsy, True)
+ t.should.be.a('function')
+ t().should.equal(true)
+
+ var f = or(Falsy)
+ f.should.be.a('function')
+ f().should.equal(false)
+
+ var f = or(Falsy, False)
+ f.should.be.a('function')
+ f().should.equal(false)
+
+ var f = or(False)
+ f.should.be.a('function')
+ f().should.equal(false)
+
+ })
+
+ it('passes context through to term', function () {
+ var term1 = sinon.stub().returns(true)
+ var term2 = sinon.stub().returns(true)
+ var t = or(term1, term2)
+ var context = {}
+
+ t.call(context)
+ term1.should.have.been.calledOn(context)
+ term2.should.not.have.been.called
+ })
+
+ it('passes arguments through to term', function () {
+ var term1 = sinon.stub().returns(true)
+ var term2 = sinon.stub().returns(true)
+ var t = or(term1, term2)
+
+ t(1, 2, 3)
+ term1.should.have.been.calledWithExactly(1, 2, 3)
+ term2.should.not.have.been.called
+ })
+
+ })
+
+})

0 comments on commit 6d039be

Please sign in to comment.