Skip to content
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

Merged
merged 1 commit into from
May 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
//.
//. R.map(S.toUpper, S.head(words))
//.
//. Sanctuary is designed to work in Node.js and in ES5-compatible browsers.
//.
//. ## Types
//.
//. Sanctuary uses Haskell-like type signatures to describe the types of
Expand Down Expand Up @@ -2746,6 +2748,52 @@
return filter(is(type), Just(x));
});

//# keys :: StrMap a -> Array String
//.
//. Returns the keys of the given string map, in arbitrary order.
//.
//. ```javascript
//. > S.keys({b: 2, c: 3, a: 1}).sort()
//. ['a', 'b', 'c']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add .sort() to the input, both to ensure determinism and to reinforce the comment about arbitrary ordering.

//. ```
S.keys =
def('keys',
{},
[$.StrMap(a), $.Array($.String)],
Object.keys);

//# values :: StrMap a -> Array a
//.
//. Returns the values of the given string map, in arbitrary order.
//.
//. ```javascript
//. > S.values({a: 1, c: 3, b: 2}).sort()
//. [1, 2, 3]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's .sort() here too.

//. ```
S.values =
def('values',
{},
[$.StrMap(a), $.Array(a)],
function(strMap) {
return Object.keys(strMap).map(function(key) { return strMap[key]; });
});

//# toPairs :: StrMap a -> Array (Pair String a)
Copy link
Member

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

//.
//. Returns the key–value pairs of the given string map, in arbitrary order.
//.
//. ```javascript
//. > S.pairs({b: 2, a: 1, c: 3}).sort()
//. [['a', 1], ['b', 2], ['c', 3]]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably .sort() here too, though relying on type coercion feels a little dirty.

Copy link
Member Author

@svozza svozza May 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way to do this that would be to list the elements in each of the functions' examples in an obviously out of order sequence. That way we don't clutter the docs with things that really aren't related to the functions:

S.keys({a: 1, b: 2, c: 3}) // => [c, b, a]
S.values({a: 1, b: 2, c: 3}) // => [1, 3, 2]
S.toPairs({a: 1, b: 2, c: 3}) //=> [['b', 2], ['a', 1], ['c', 3]]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The root problem is that we're using an ordered collection for something with no inherent order. When comparing two arrays for equality, order is of course significant. To ensure the doctests pass, we have two options:

  • rely on property enumeration order; or
  • use Array#sort.

Thinking about this again, I believe the best approach is to jumble the keys in the input:

> S.keys({c: 3, a: 1, b: 2}).sort()
['a', 'b', 'c']

> S.values({c: 3, a: 1, b: 2}).sort()
[1, 2, 3]

> S.pairs({c: 3, a: 1, b: 2}).sort()
[['a', 1], ['b', 2], ['c', 3]]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, I'd forgotten about the doc tests.

//. ```
S.pairs =
def('pairs',
{},
[$.StrMap(a), $.Array($.Pair($.String, a))],
function(strMap) {
return Object.keys(strMap).map(function(k) { return [k, strMap[k]]; });
});

//. ### Number

//# negate :: ValidNumber -> ValidNumber
Expand Down
59 changes: 59 additions & 0 deletions test/keys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use strict';

var throws = require('assert').throws;

var eq = require('./utils').eq;
var errorEq = require('./utils').errorEq;
var S = require('..');


describe('keys', function() {

it('is a unary function', function() {
eq(typeof S.keys, 'function');
eq(S.keys.length, 1);
});

it('type checks its arguments', function() {
throws(function() { S.keys('xxx'); },
errorEq(TypeError,
'Invalid value\n' +
'\n' +
'keys :: StrMap a -> Array String\n' +
' ^^^^^^^^\n' +
' 1\n' +
'\n' +
'1) "xxx" :: String\n' +
'\n' +
'The value at position 1 is not a member of ‘StrMap a’.\n'));

throws(function() { S.keys({a: '1', b: 2, c: '3'}); },
errorEq(TypeError,
'Type-variable constraint violation\n' +
'\n' +
'keys :: StrMap a -> Array String\n' +
' ^\n' +
' 1\n' +
'\n' +
'1) "1" :: String\n' +
' 2 :: Number, FiniteNumber, NonZeroFiniteNumber, Integer, ValidNumber\n' +
' "3" :: String\n' +
'\n' +
'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n'));
});

it("returns an array of the given object's own keys", function() {
eq(S.keys({}), []);
eq(S.keys({a: 1, b: 2, c: 3}).sort(), ['a', 'b', 'c']);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's worth noting that sorted keys would be useful here. ;)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how it would have made it any easier to copy and paste this test from Ramda's. ;)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also test S.keys({}).

});

it('does not include prototype properties', function() {
var proto = {a: 1, b: 2};
var obj = Object.create(proto);
obj.c = 3;
obj.d = 4;

eq(S.keys(obj).sort(), ['c', 'd']);
});

});
63 changes: 63 additions & 0 deletions test/pairs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use strict';

var throws = require('assert').throws;

var eq = require('./utils').eq;
var errorEq = require('./utils').errorEq;
var S = require('..');


describe('toPairs', function() {

var comparePairsAsc = function(a, b) {
return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
};

it('is a unary function', function() {
eq(typeof S.pairs, 'function');
eq(S.pairs.length, 1);
});

it('type checks its arguments', function() {
throws(function() { S.pairs('xxx'); },
errorEq(TypeError,
'Invalid value\n' +
'\n' +
'pairs :: StrMap a -> Array (Pair String a)\n' +
' ^^^^^^^^\n' +
' 1\n' +
'\n' +
'1) "xxx" :: String\n' +
'\n' +
'The value at position 1 is not a member of ‘StrMap a’.\n'));

throws(function() { S.pairs({a: '1', b: 2, c: '3'}); },
errorEq(TypeError,
'Type-variable constraint violation\n' +
'\n' +
'pairs :: StrMap a -> Array (Pair String a)\n' +
' ^\n' +
' 1\n' +
'\n' +
'1) "1" :: String\n' +
' 2 :: Number, FiniteNumber, NonZeroFiniteNumber, Integer, ValidNumber\n' +
' "3" :: String\n' +
'\n' +
'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n'));
});

it('returns an array with the key value pairs of each property of the object', function() {
eq(S.pairs({}), []);
eq(S.pairs({a: 1, b: 2, c: 3}).sort(comparePairsAsc), [['a', 1], ['b', 2], ['c', 3]]);
});

it('does not include prototype properties', function() {
var proto = {a: 1, b: 2};
var obj = Object.create(proto);
obj.c = 3;
obj.d = 4;

eq(S.pairs(obj).sort(comparePairsAsc), [['c', 3], ['d', 4]]);
});

});
59 changes: 59 additions & 0 deletions test/values.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use strict';

var throws = require('assert').throws;

var eq = require('./utils').eq;
var errorEq = require('./utils').errorEq;
var S = require('..');


describe('values', function() {

it('is a unary function', function() {
eq(typeof S.values, 'function');
eq(S.values.length, 1);
});

it('type checks its arguments', function() {
throws(function() { S.values('xxx'); },
errorEq(TypeError,
'Invalid value\n' +
'\n' +
'values :: StrMap a -> Array a\n' +
' ^^^^^^^^\n' +
' 1\n' +
'\n' +
'1) "xxx" :: String\n' +
'\n' +
'The value at position 1 is not a member of ‘StrMap a’.\n'));

throws(function() { S.values({a: '1', b: 2, c: '3'}); },
errorEq(TypeError,
'Type-variable constraint violation\n' +
'\n' +
'values :: StrMap a -> Array a\n' +
' ^\n' +
' 1\n' +
'\n' +
'1) "1" :: String\n' +
' 2 :: Number, FiniteNumber, NonZeroFiniteNumber, Integer, ValidNumber\n' +
' "3" :: String\n' +
'\n' +
'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n'));
});

it("returns an array of the given object's own values", function() {
eq(S.values({}), []);
eq(S.values({a: 1, b: 2, c: 3}).sort(), [1, 2, 3]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also test S.values({}).

});

it('does not include prototype values', function() {
var proto = {a: 1, b: 2};
var obj = Object.create(proto);
obj.c = 3;
obj.d = 4;

eq(S.values(obj).sort(), [3, 4]);
});

});