separate R.where into two simpler functions: R.where and R.whereEq #1036

Merged
merged 1 commit into from May 8, 2015

Projects

None yet

4 participants

@davidchambers
Member

Closes #1032

This is the alternative to #1034. If we decide we want both functions we need to decide what to name them. :)

@davidchambers davidchambers and 1 other commented on an outdated diff Apr 20, 2015
test/where.js
@@ -50,17 +27,13 @@ describe('where', function() {
});
it('is false if the test object is null-ish', function() {
@davidchambers
davidchambers Apr 20, 2015 Member

I'd love to remove this special case.

@CrossEye CrossEye and 1 other commented on an outdated diff Apr 20, 2015
*/
module.exports = _curry2(function where(spec, testObj) {
- var parsedSpec = groupBy(function(key) {
- return typeof spec[key] === 'function' ? 'fn' : 'obj';
- }, keys(spec));
-
- return _satisfiesSpec(spec, parsedSpec, testObj);
+ testObj = Object(testObj);
+ for (var prop in spec) {
+ if (_has(prop, spec) && !spec[prop](testObj[prop])) {
@CrossEye
CrossEye Apr 20, 2015 Member

I would still rather pass the object as well. We clearly need to hash this out. The triangle inequality example deleted below is a reasonable example of why.

@davidchambers
davidchambers Apr 20, 2015 Member

My view is that multiple-field constraints are better expressed in terms of functions which take the whole object. The (value, object) parameters strike me as awkward: if one has access to the whole object, value isn't necessary. The counterargument is likely that value is convenient in the common case. To this I argue that R.where should focus on the common case and let multiple-field constraints be expressed in different ways.

With R.where:

// valid :: { a :: Number, b :: Number, c :: Number } -> Boolean
var valid = R.where({
  a: function(a, obj) {
    return a < obj.b + obj.c;
  },
  b: function(b, obj) {
    return b < obj.a + obj.c;
  },
  c: function(c, obj) {
    return c < obj.a + obj.b;
  }
});

Without R.where:

// valid :: { a :: Number, b :: Number, c :: Number } -> Boolean
var valid = function(obj) {
  return (obj.a < obj.b + obj.c &&
          obj.b < obj.a + obj.c &&
          obj.c < obj.a + obj.b);
};

Or even:

// valid :: { a :: Number, b :: Number, c :: Number } -> Boolean
var valid = R.pipe(R.props(['a', 'b', 'c']),
                   R.sortBy(R.identity),
                   R.converge(R.lt,
                              R.last,
                              R.pipe(R.init, R.sum)));
@buzzdecafe
Member

IMO where is about specifying an interface that the input object must satisfy, not that the object's individual properties must satisfy in isolation.

@megawac
Contributor
megawac commented Apr 21, 2015

I'm fine with this as long as whereEq is included

whereEq = compose(where, mapObject(eq))
@CrossEye
Member

Damn, it hadn't hit me how straightforward that conversion was:

whereEq = compose(where, mapObject(eq))

Nice.

@svozza svozza referenced this pull request Apr 21, 2015
Closed

Highland.js Integration #1037

@davidchambers
Member

Shall I add R.whereEq?

@CrossEye
Member

I could be happy with this version of where alongside a whereEq. But I believe @buzzdecafe still might have some reservations about it.

@buzzdecafe
Member

not reservations. just not seeing the point particularly.

@CrossEye
Member

I say yes, then. Two simple functions beat one complex function hands-down.

@davidchambers davidchambers changed the title from simplify R.where to separate R.where into two simpler functions: R.where and R.whereEq May 4, 2015
@davidchambers
Member

I've added R.whereEq.

@davidchambers davidchambers commented on the diff May 4, 2015
src/whereEq.js
@@ -0,0 +1,35 @@
+var _curry2 = require('./internal/_curry2');
+var eq = require('./eq');
+var mapObj = require('./mapObj');
+var where = require('./where');
+
+
+/**
+ * Takes a spec object and a test object; returns true if the test satisfies
+ * the spec, false otherwise. An object satisfies the spec if, for each of the
+ * spec's own properties, accessing that property of the object gives the same
+ * value (in `R.eq` terms) as accessing that property of the spec.
+ *
+ * `whereEq` is a specialization of [`where`](#where).
@davidchambers
davidchambers May 4, 2015 Member

Is this valid use of the word specialization?

@CrossEye
CrossEye May 4, 2015 Member

I would say so.

@davidchambers
Member
@buzzdecafe
Member

i don't mind the complexity of the original. i don't really object to this, but it doesn't excite me either. i expect this may break a lot of stuff that uses where at present.

this is nice: return where(mapObj(eq, spec), testObj);
but i wonder how it performs? that was the reason for the nastiness of the original.

@davidchambers
Member

i expect this may break a lot of stuff that uses where at present.

Certainly. In most cases, users will need to replace R.where with R.whereEq:

-R.where({x: 1, y: 2})
+R.whereEq({x: 1, y: 2})

In each of the remaining cases, each non-function value in the spec must be wrapped with R.eq:

-R.where({x: 1, y: 2, z: R.gte(R.__, 0)})
+R.where({x: R.eq(1), y: R.eq(2), z: R.gte(R.__, 0)})
@CrossEye
Member
CrossEye commented May 8, 2015

Sorry, I thought I'd made it clear that I was in favor of this.

I do think that sometime soon, we'll have to look at the basic implementation. If I'm not mistaken, in the Great Curry Debacle of 2014, we reset this one in such a way that it might have lost some of the benefits that had been gained by the initial manual currying we had done. This one was the function that showed us the need for changes to our old currying, and it had some interesting behavior...

@buzzdecafe buzzdecafe merged commit 75cc61b into ramda:master May 8, 2015

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
@megawac
Contributor
megawac commented May 8, 2015

Let's just make sure this is bold, highlighted, underlined in change log as
it will break a good amount of code
On May 8, 2015 10:46 AM, "Michael Hurley" notifications@github.com wrote:

Merged #1036 #1036.


Reply to this email directly or view it on GitHub
#1036 (comment).

@buzzdecafe
Member

yep, totally agree

@davidchambers davidchambers deleted the davidchambers:where-pred branch May 8, 2015
@buzzdecafe
Member

not sure what to make of this: http://jsperf.com/mapobj-test

@davidchambers
Member

I could certainly get behind a more efficient implementation. It was API complexity this pull request sought to reduce.

@megawac megawac commented on the diff May 9, 2015
src/where.js
*/
module.exports = _curry2(function where(spec, testObj) {
- var parsedSpec = groupBy(function(key) {
- return typeof spec[key] === 'function' ? 'fn' : 'obj';
- }, keys(spec));
-
- return _satisfiesSpec(spec, parsedSpec, testObj);
+ for (var prop in spec) {
+ if (_has(prop, spec) && !spec[prop](testObj[prop])) {
@megawac
megawac May 9, 2015 Contributor

Any reason you're not using keys here (it'd be faster)?

@davidchambers
davidchambers May 9, 2015 Member

Sounds like a good idea to me!

This was referenced May 12, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment