New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
remove R.isEmpty #1228
Comments
🐄 |
I know this one has had it's share of controversy. But I'm in favor of keeping it. Saying "abc is sugar for xyz" is different from saying "abc can be implemented as xyz". I think this is the latter case. " But this case is different. The idea is expressed by So I would rather keep the name. It is reasonably documented. |
👍 All the cases shows that people confuse the name of this function with what it does. It implies something else. We should not keep a confusing function just because we might one day implement something that does what it says. If that is the goal we should make that change now or remove it in the mean time to avoid harm. |
The fact that the meaning of |
With @CrossEye here. I think the function is clearly documented. If it is considered a problem that people make incorrect assumptions about functions by just looking at the name then a whole lot of ramda functions are in trouble. I don't think |
I'm with @CrossEye too. The documentation could not be clearer, if people want to make assumptions then that's their problem. |
Mostly to me it's about expressive code. This: var display = R.ifElse(R.isEmpty, R.always('no values'), R.join(', ')); demonstrates my intent much better than this: var display = R.ifElse(R.propEq('length', 0), R.always('no values'), R.join(', ')); |
I think I might be the "some" @davidchambers is referring to as I just made this mistake :) And I do think something should be changed.
The thing is, I don't feel there is much (any?) precedence that Ruby: http://stackoverflow.com/a/20677846/342588 If I saw some code
, looks fine. But if I also saw
, I wouldn't think "what is this crazy person doing, isEmpty doesn't make any sense with an object". In python, all the "empty" stuff evaluates to false. In ruby you can use |
Perhaps if var isEmpty = R.converge(R.equals, R.empty, R.identity);
isEmpty([]); // true
isEmpty([1]); // false It won't help with the var Mul = function(x) { this.x = x };
Mul.prototype.empty = function() { return new Mul(1); }
Mul.prototype.concat = function(m) { return m.x * this.x };
Mul.prototype.equals = function(m) { return m.x === this.x };
var one = new Mul(1);
var two = new Mul(2);
isEmpty(one); // true
isEmpty(two); // false |
I would have no problem approaching that again. We discussed this in #533, #538, and #539. In that last one, we realized just how confusing the notion was for objects and ended up dropping it. If we can find something we can live with, I think it would be quite reasonable to say we return We might just have to live with (and document) the sort of weird cases such as the one I mentioned in #539: var obj = Object.create(null);
Object.defineProperty(obj, 'foo', {
enumerable: false,
configurable: true,
writable: true,
value: 'bar'
});
obj; //=> {foo: 'bar'}
isEmpty(obj); //=> true - Really? |
Interesting idea! |
I don't find it confusing. Nonenumerable properties are nonenumerable exactly because they should not be part of such things.
That is a splendid idea! Then we just need to fix |
I am not saying that
would be a bad idea or incorrect. But thinking about it, I think I would prefer a function named |
Well, I for one would still find this surprising: typeof obj; //=> "object"
obj.foo; //=> "bar"
R.isEmpty(obj); //=> true But the main objection was to this:
This does not feel at all like the sort of API I want to document, maintain, and support. The last bit is the most problematic: "objects with no enumerable own-properties" sounds more like technical prestidigitation than clear definition. It might be the only thing really possible given the way the language works, but it's certainly not something to love. |
An alternative way to state this might be in terms of |
That's clearly better. But I feel as though it's skirting the issue a bit. It feels as if there should be invariants that one could count on: if R.length(x); //=> 0
x[0]; //=> undefined
x[42]; //=> undefined If R.prop('length', x); //=> 0
R.nthChar(0, x); //=> ""
R.nthChar(42, x); //=> "" And if R.length(R.keys(x)); //=> 0
x[""]; //=> undefined
x.foo; //=> undefined But of course we can't get those final kinds of invariants for the object case, and it feels strange. I actually do want to do this, even if we have to live with such ugliness, but I want us to go in with our eyes open to just how ugly this is. We should not sweep this under the rug. Most of Ramda's API is clean; a lot of it is elegant. This is neither. |
I would certainly not. If
That is exactly the same just worded differently since |
If I received an object from a distant part of the system, something not necessarily in my control, I have no idea that someone might have done this intentionally. I understand the language, and do know that such is possible, I've had little call to actually create non-enumerable properties. I can easily imagine requirements that discuss what happens based on various combinations of the values of the
If someone passed in an object with a non-enumerable
I was discussing the wording. I want to know how to document such a function without making it sound like a laundry list of unrelated functionality. |
You cannot use misuse of non-enumerable properties as an argument against |
I'm not trying to argue against this. I'm trying to make sure that we recognize that what we're stepping in. I want to do this. But I don't want us to do this, thinking it's actually simple. This is, in fact, going to be one of the ugliest APIs of all the functions in Ramda. |
I don't think where stepping in on anything. People have been able to misuse non-enumerable properties ever since ECMAScript 5. I don't see how this changes anything.
I disagree. This function is very simple. It compares anything that is a setoid and a monoid to its identity element. What is ugly about that? I think this change recognized the bigger more generalized picture and is a step away from the very common overly specialized array-only functions. |
I can define a setoid for the integers quite simply: two integers are defined as equal if they have the same remainder when divided by 5. So 12, 17, -23, and 4192 are all equal under this definition. I can easily make a monoid out of this, and in fact a full group with modular addition. Or I can define a setoid for the complex numbers by defining them as equal if their real parts are equal. Again, it's easy to create a monoid from this. In neither case can I be said to have clearly defined the setoid for the domain though. It's clear enough that I'm losing information that might be valuable. 17 and 4192 are in fact different numbers. 4.2 + 3.7i and 4.2 - 6.5i are not identical. If our setoid for JS objects defines two objects equal, even though they have properties that differ ( For strings and arrays, there are clearly obvious setoid definitions. This is simply not true for Javascript objects. (The issues we've had with |
Is this a wanted direction to go in? Is there a link to a discussion about this? I prefer the highly specialized functions that ramda provide in opposite to more overloaded ones. Why not keep |
I can also do this: var a1 = [1,2,3];
var a2 = [1,2,3];
Object.defineProperty(a1, 'key', {
enumerable: false,
configurable: true,
writable: true,
value: 'val'
});
R.equals(a1, a2); //=> true That also loses information. To me neither the object nor the array case are of any significance and I don't feel uncomfortable by any of them.
That is a matter of what one finds obvious. To me the definition is obvious.
That is not my impression. |
I think overall it's not. I agree that Ramda should continue to specialize in functions with simple APIs, usually taking single input types for any parameter. I had started to agree that this function was somewhat different. This discussion is making that harder to accept, but in the end I think it's still probably the best bet for this reason:
|
Although we still haven't documented it as well as we should, Ramda's philosophy is that its data type is a list and if you pass it an array that does not represent a list, then anything might happen. we haven't so far tried to make any such claims about plain object.
I really misspoke. There is a clear and obvious setoid definition for objects: Two objects are equal if they have the same properties. (We could get more formal about this if we really wanted, but I'm sure you understand what I mean.) The trouble is that we have no way within the language to test this style of equality. It is because of this that we are stuck with the nasty "enumerable own-properties" formulation, or @scott-christopher's more palatable gloss on it. |
@CrossEye I think I raised this in another issue but my expectation would be that if a function is not used according to its contract then the behavior should be undefined. Now, one problem with
The examples seem to tell a different more complex story though. If the examples only showed There is the So to your points I say:
|
@TheLudd: I'm more than half-way convinced, based on all the controversy here. Ramda hasn't gone down this path often, and doing so here seems to be quite painful. @buzzdecafe, @davidchambers, @raine: Any thoughts on this? |
I prefer #1242, but if we decide not to merge that pull request I'd rather deprecate |
I'm with @TheLudd on this. |
My preferred solution to the null/undefined issue is to add type checking throughout the library: R.map(null); // => TypeError: map requires a value of type Function as its first argument; received null
R.isEmpty(null); // => TypeError: Cannot apply isEmpty to null |
If there is a concern regarding non-enumerable properties when passing objects to var obj1 = { a: 1 };
var obj2 = Object.create(null, {
a: { value: 1, enumerable: true },
b: { value: 2, enumerable: false }
});
R.equals(obj1, obj2); // true Regarding passing |
That is not true. One can use |
I opened #1252 to show my suggestion on how to improve |
We have actually discussed this before in #481 and while I would like to have that functionality it can be difficult to achieve with good error messages like the ones in your examples. Because some functions are just composed from other functions. This was one of the problems I ran into when trying to work it out earlier. That being said, if ramda would like to go in this direction I would support and help to the best of my abilities. |
Note the "Own" in that name. I suppose if we're operating in an environment which has |
I addressed this problem with help from @paldepind in sanctuary-js/sanctuary#50, and have since made improvements in sanctuary-js/sanctuary#61 and sanctuary-js/sanctuary#63. I'm convinced that Ramda should follow suit. @paldepind is on board. The next step is to convince @buzzdecafe and @CrossEye. ;) I've been more productive since adding type checking to Sanctuary, in part because my colleagues stopped asking me to debug type mismatches in pipelines. This is a good thing because I don't particularly enjoy being a human type checker! |
As a result of #1242 I'm no longer in favour of removing |
R.isEmpty
is sugar forR.propEq('length', 0)
. The problem, though, is that people don't necessary realize that this is the case. Some might assume, for example, thatR.isEmpty({})
evaluates true (#538, #1194). A colleague just ran into problems by assumingR.isEmpty(null)
andR.isEmpty(undefined)
evaluate true.Sugar is nice, but in this case the desugaring is not obvious.
The text was updated successfully, but these errors were encountered: