docs: function difference(objA, objB) {
  
  	var valuesA = objA.values,
  		valuesB = objB.values,
  		diff = keyDiff(valuesA, valuesB),
  		aOnlyKeys = diff.aOnlyKeys,
  		aAndBKeys = diff.aAndBKeys,
  		bOnlyKeys = diff.bOnlyKeys;
  
  	// check if all aAndB are equal
  
  	// With the shared keys, perform vA \ vB difference. If the DIFFERENCE is:
  	// - EMPTY: vA has nothing outside vB. vA is equal or subset of vB.
  	//   - IF sB has keys not in sA, the shared keys will be part of the result;
  	//     OTHERWISE, if all empty, sA is subset of sB, EMPTY will be returned
  	//                (even if sA has some extra own keys)
  	// - NON-EMPTY: something in sA that is not in sB
  	//   Now we need to figure out if it's "product-able" or not.
  	//   Product-able -> some part of B is in A.
  	//   Perform B ∩ A intersection.  INTERSECTION is:
  	//   - EMPTY: NOT "product-able". DISJOINT.  Must return something.
  	//   - non-EMPTY: Use to performa  product (in the future.)
  	var sharedKeysAndValues = {},
  		productAbleKeysAndData = {},
  		disjointKeysAndValues = {};
  	aAndBKeys.forEach(function(key) {
  		var difference = set.difference(valuesA[key], valuesB[key]);
  		if (difference === set.EMPTY) {
  			sharedKeysAndValues[key] = valuesA[key];
  		} else {
  			var intersection = set.intersection(valuesA[key], valuesB[key]);
  			var isProductable = intersection !== set.EMPTY
  			if (isProductable) {
  				productAbleKeysAndData[key] = {
  					// Products with `difference U intersection` would be subtracted
  					// from produts with `intersection`
  					difference: difference,
  					intersection: intersection
  				}
  			} else {
  				disjointKeysAndValues[key] = valuesA[key];
  			}
  		}
  	});
  	var productAbleKeys = Object.keys(productAbleKeysAndData);
  	var singleProductKeyAndValue;
  	if (productAbleKeys.length === 1) {
  		singleProductKeyAndValue = {};
  		singleProductKeyAndValue[productAbleKeys[0]] = productAbleKeysAndData[productAbleKeys[0]].difference;
  	}
  
  	// Now that we've got the shared keys organized
  	// we can make decisions based on this information
  	// and A-only and B-only keys.
  
  	// if we have any disjoint keys, these sets can not intersect
  	// {age: 21, ...} \ {age: 22, ...} ->  {age: 21, ...}
  	if (Object.keys(disjointKeysAndValues).length) {
  		return objA;
  	}
  
  	// contain all the same keys
  	if ((aOnlyKeys.length === 0) && (bOnlyKeys.length === 0)) {
  		if (productAbleKeys.length > 1) {
  			return set.UNDEFINABLE;
  		}
  		// {color: [RED, GREEN], ...X...} \ {color: [RED], ...X...} -> {color: [GREEN], ...X...}
  		else if (productAbleKeys.length === 1) {
  			assign(sharedKeysAndValues, singleProductKeyAndValue);
  			return new KeysAnd(sharedKeysAndValues);
  		} else {
  			// {...X...} \ {...X...} -> EMPTY
  			return set.EMPTY;
  		}
  	}
  	// sA is likely a subset of sB
  	if (aOnlyKeys.length > 0 && bOnlyKeys.length === 0) {
  		if (productAbleKeys.length > 1) {
  			return set.UNDEFINABLE;
  		}
  		// {age: 35, color: [RED, GREEN], ...X...} \ {color: [RED], ...X...} -> {age: 35, color: [GREEN], ...X...}
  		else if (productAbleKeys.length === 1) {
  			assign(sharedKeysAndValues, singleProductKeyAndValue);
  			aOnlyKeys.forEach(function(key) {
  				sharedKeysAndValues[key] = valuesA[key];
  			});
  			return new KeysAnd(sharedKeysAndValues);
  		} else {
  			// sharedKeysAndValues
  			return set.EMPTY;
  		}
  	}
  	// sB is likely subset of sA
  	// {}, {foo: "bar"} -> {foo: NOT("bar")}
  	if (aOnlyKeys.length === 0 && bOnlyKeys.length > 0) {
  		// Lets not figure out productAbleKeys right now.
  		// Example:
  		// {color: [RED, GREEN], ...X...}
  		// \ {age: 35, color: [RED], ...X...}
  		// = OR( {color: [GREEN], ...X...}, {age: NOT(35), color: [RED], ...X...} )
  		if (productAbleKeys.length > 1) {
  			return set.UNDEFINABLE;
  		}
  		var productAbleOr;
  		if (productAbleKeys.length === 1) {
  			// we add the intersection to the AND
  			// the difference is the or
  			var productableKey = productAbleKeys[0];
  			productAbleOr = assign({}, sharedKeysAndValues);
  			productAbleOr[productableKey] = productAbleKeysAndData[productableKey].difference;
  			sharedKeysAndValues[productableKey] = productAbleKeysAndData[productableKey].intersection;
  		}
  
  		var ands = bOnlyKeys.map(function(key) {
  			var shared = assign({}, sharedKeysAndValues);
  			var result = shared[key] = set.difference(set.UNIVERSAL, valuesB[key]);
  			return result === set.EMPTY ? result : new KeysAnd(shared);
  		}).filter(notEmpty);
  
  		if (productAbleOr) {
  			ands.push(new KeysAnd(productAbleOr))
  		}
  
  		// {c: "g"}
  		// \ {c: "g", age: 22, name: "justin"}
  		// = OR[ AND(name: NOT("justin"), c:"g"), AND(age: NOT(22), c: "g") ]
  		if (ands.length > 1) {
  			return new keysLogic.ValuesOr(ands);
  		} else if (ands.length === 1) {
  			// {c: "g"}
  			// \ {c: "g", age: 22}
  			// = AND(age: NOT(22), c: "g")
  			return ands[0];
  		} else {
  			return set.EMPTY;
  		}
  	}
  
  	// {name: "Justin"} \\ {age: 35} -> {name: "Justin", age: NOT(35)}
  	if (aOnlyKeys.length > 0 && bOnlyKeys.length > 0) {
  		if (productAbleKeys.length) {
  			throw new Error("Can't handle any productable keys right now")
  		}
  		// add everything in sA into the result:
  		aOnlyKeys.forEach(function(key) {
  			sharedKeysAndValues[key] = valuesA[key];
  		});
  
  		if (bOnlyKeys.length === 1) {
  			// TODO: de-duplicate below
  			var key = bOnlyKeys[0];
  			var shared = assign({}, sharedKeysAndValues);
  			shared[key] = set.difference(set.UNIVERSAL, valuesB[key]);
  			return new KeysAnd(shared);
  		}
  		// {foo: "bar"} \\ {name: "Justin", age: 35} -> UNDEFINABLE
  		else {
  			return set.UNDEFINABLE;
  		}
  
  	}
  }