-
-
Notifications
You must be signed in to change notification settings - Fork 95
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
Add S.keys, S.values and S.pairs #214
Conversation
I'm excited to see these functions; they'll make great additions to the library! I'd also like to provide We could rely on the default behaviour of
The Ramda implementation is complicated because it works around bugs in certain old browser versions. We needn't do this. The ES3-compatible implementations needn't be complex: // keys :: StrMap a -> Array String
var keys = function(strMap) {
var ks = [];
for (var k in strMap) if (has(k, strMap)) ks.push(k);
return ks.sort();
}
// values :: StrMap a -> Array a
var values = function(strMap) {
var ks = keys(strMap);
var vs = [];
for (var idx = 0; idx < ks.length; idx += 1) {
vs.push(strMap[ks[idx]]);
}
return vs;
};
// pairs :: StrMap a -> Array (Pair String a)
var pairs = function(strMap) {
var ks = keys(strMap);
var kvs = [];
for (var idx = 0; idx < ks.length; idx += 1) {
kvs.push([ks[idx], strMap[ks[idx]]]);
}
return kvs;
}
Type checking is currently very slow. I'm confident we can reduce the cost by at least 90%. Once one of us spends a few days making the type checking less naive we'll hopefully be able to stop worrying about paying the cost twice. |
What about using @svozza's implementations for these names, and also adding And what about a simple |
I'm open to providing "fast" versions of these functions as well, but I'd like to name them The other option is to sidestep the ordering problem by defining Set and Bag types. We could then define: keys :: StrMap a -> Set String
values :: StrMap a -> Bag a
pairs :: StrMap a -> Set (Pair String a) I'd like to stick with arrays, though, so we could guarantee this property:
|
For me the faster versions make sense with the plain names because
But I see the appeal of ordered results to these functions.. I'm okay either way 🤷 |
I might be on the wrong side of the fence here. :) What do others think? @Avaq? @benperez? @scott-christopher? I'm also interested in your thoughts on the matter, @CrossEye, since we've discussed this in Ramda threads several times. |
f984324
to
7070ee8
Compare
I've ES3-ified the implementation. Do you want me to add Regarding sorting the keys, it seems odd to impose an order of keys in a map, if the user really cares she should just sort the result herself. |
Thanks. We're still relying on
If you'd like to do so, sure! It's up to you. :)
Sorting the array returned by The question is whether to support diverging/converging pipelines such as this: {x: 0, y: 42}
/ \
/ \
/ \
/ \
/ \
/ \
['x', 'y'] <= keys values => [0, 42]
| |
| |
| |
| |
| |
| |
['X', 'Y'] <= map(toUpper) map(inc) => [1, 43]
\ /
\ /
\ /
\ /
\ /
\ /
zip => [['X', 1], ['Y', 43]]
|
|
|
|
|
|
fromPairs => {X: 1, Y: 43} If we decide not to guarantee consistent ordering, the user can of course use this approach instead: {x: 0, y: 42}
|
|
|
|
pairs => [['x', 0], ['y', 42]]
|
|
|
|
map(over(lensIndex(0), toUpper)) => [['X', 0], ['Y', 42]]
|
|
|
|
map(over(lensIndex(1), inc)) => [['X', 1], ['Y', 43]]
|
|
|
|
fromPairs => {X: 1, Y: 43} Both approaches seem fine to me. If we support both, users are free to use whichever feels more natural. Forcing users to think about such flows in a certain way doesn't appeal to me (though I realize Sanctuary is an opinionated library which forces users to think a certain way in many other contexts). Does this clarify my position, @svozza? As I say, I might be on the wrong side of this disagreement. ;) |
@@ -362,6 +364,14 @@ | |||
} | |||
}); | |||
|
|||
// keys :: StrMap a -> Array String | |||
var keys = function(strMap) { | |||
if (typeof Object.keys === 'function') return Object.keys(strMap); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this for performance? My default position is to minimize branching, but I'm happy to introduce branching if there's a significant performance difference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember reading before that Object.keys
is heavily optimised but I can't for the life of me remember where. for...in
loops are supposed to be slow too, especially the hasOwnProperty
guard. Although tbh, I'm not sure any of that matters, I can't imagine people using this function on objects with enough keys for it to be an issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would advice against optimizing performance before
- It's established that there is a performance issue
- It's established that the optimization fixes it
- The optimized code behaves exactly the same as the non-optimized code
That said, a quick performance benchmark on my local machine shows that Object.keys
is about 5 times as fast as for ... in hasOwn
, so that certainly crosses out 2
from my list. ;)
I'm a little worried about the behavior differences between Object.keys
and for ... in
as illustrated by the MDN suggested polyfill. Most of it seems to be related to some bug where for ... in
misses some properties that should have been enumerated though. It probably won't affect us.
My vote goes in favor of the Object.keys
branch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're to branch, would it make sense to do the check once only?
var keys = typeof Object.keys === 'function' ? Object.keys : function(strMap) {…};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚡ Although I'm not sure if I've got the formatting right...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mocha runs the tests in parallel
I don't think that's the case. The problem is that Node itself uses Object.keys
, so deleting it can cause problems. For example:
> delete Object.keys
true
> console.log({})
TypeError: Object.keys is not a function
I don't have a good solution to this problem. The simplest option is to remove the "fast" path, but I'm open to other suggestions. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather remove the slow path tbh.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you really want to test this branch and don't mind restricting your test suite to run in Node only, you could potentially execute the test in a new VM context.
vm.runInNewContext('console.log(Object.keys)', R.dissocPath(['Object', 'keys'], this))
That said, how desirable is ES3 support?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That said, how desirable is ES3 support?
Dropping support for ES3 seems popular. I'm happy to do so, though I'd like to add a section to the readme to document which environments we target. The description could be as brief as Sanctuary is designed to work in Node and in ES5-compatible browsers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚡
I can see where you're coming from with the sorted keys but to me I think if you care about how the elements line up you're far more likely to work on them as pairs, as per your second example and probably more like this: const f = (pair) => [R.toUpper(pair[0]), R.inc(pair[1])];
map(f, toPairs({x: 1, y: 2})); I have a slight preference for leaving the resulting arrays unsorted as iterating over the list three times (if you include the |
On the one hand, I think that it would be nice if Sanctuary avoided non-deterministic functions. On the other hand, it seems odd for Sanctuary to impose arbitrary structure on values. I think I like the |
for (var idx = 0; idx < ks.length; idx += 1) { | ||
vs.push(strMap[ks[idx]]); | ||
} | ||
return vs; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're thinking about performance, I wonder whether reusing the keys array would help:
var xs = keys(strMap);
for (var idx = 0; idx < xs.length; idx += 1) xs[idx] = strMap[xs[idx]];
return xs;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea! ⚡
ef9e4b0
to
9ce4752
Compare
Very exciting! I'm not sure about the name, though. In Data.HashMap the three functions are named Since all the Sanctuary functions live in a single namespace |
Yep, I prefer |
@@ -2746,6 +2748,59 @@ | |||
return filter(is(type), Just(x)); | |||
}); | |||
|
|||
//# keys :: StrMap a -> Array String | |||
//. | |||
//. Returns an array of the keys of the supplied StrMap. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could omit "an array of" as this is indicated by the type signature.
Let's append "in arbitrary order".
⚡ Think I've everything except adding |
4490c12
to
d97ec53
Compare
function(strMap) { | ||
var xs = Object.keys(strMap); | ||
for (var idx = 0; idx < xs.length; idx += 1) xs[idx] = strMap[xs[idx]]; | ||
return xs; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about using Array#map
here too?
return Object.keys(strMap).map(function(k) { return strMap[k]; });
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ha. We've come full circle to my original implementation!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep! Now that we're embracing ES5 we can take advantage of its conveniences. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, all good!
🌳 Very nice pull request, Stefano! |
return Object.keys(strMap).map(function(key) { return strMap[key]; }); | ||
}); | ||
|
||
//# toPairs :: StrMap a -> Array (Pair String a) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/toPairs/pairs/
I'll fix this shortly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating a type-safe version of
R.keys
was mentioned in #142 and now thatStrMap
is available I thought I'd road test it a bit. The implementation of this will probably provoke discussion, firstly because I'm using an ES5 only implementation: I find the Ramda implementation far too complicated for what should be a simple function. Secondly, I've chosen not to implementS.values
in terms ofS.keys
because I don't think it makes sense to pay the penalty of type checking the object twice.