consider ignoring inherited properties in StrMap implementations
See original GitHub issue@gabejohnson in https://github.com/sanctuary-js/sanctuary/pull/435#discussion_r135419232:
I don’t know that we necessarily should match the semantics of
S.concat
as it works on objects in general. I think we may [inadvertently] cause confusion by not explicitly definingStrMap
. In my opinion inherited properties should not be copied for aStrMap
.
This is worth discussing. Here’s an example which is not completely contrived:
function DigitCounter() {}
DigitCounter.count = ($counter, s) => {
for (let idx = 0; idx < s.length; idx += 1) {
const c = s[idx];
if (/\d/.test(c)) $counter[c] += 1;
}
};
for (let n = 0; n <= 9; n += 1) {
DigitCounter.prototype[n] = 0;
}
const $counter = new DigitCounter();
DigitCounter.count($counter, String(Math.PI));
$counter;
// => {'1': 2, '2': 1, '3': 3, '4': 1, '5': 3, '6': 1, '7': 1, '8': 1, '9': 3}
Z.map(x => x, $counter);
// => {'0': 0, '1': 2, '2': 1, '3': 3, '4': 1, '5': 3, '6': 1, '7': 1, '8': 1, '9': 3}
Note that mapping x => x
over $counter
“promotes” the inherited property to an own property. The question is whether the inherited property should be ignored.
I believe the current behaviour is not just defensible but preferable. My reasoning is as follows:
-
We currently use
for ... in
which respects all enumerable properties (both own and inherited). It ignores non-enumerable properties such astoString
andvalueOf
. It’s unlikely that people are affected one way or the other by our handling of enumerable inherited properties; I imagine that close to 100% of string maps are plain objects. -
Although obscure, the example above shows that respecting enumerable inherited properties can be useful. We should not disallow such code without good reason.
-
Z.concat
is one of the library’s foundational functions. Performing ahasOwnProperty
check for each key would affect the performance of several other functions as well as ofZ.concat
itself.
I look forward to understanding your reasons for suggesting that all <del>enumerable</del> inherited properties should be ignored, @gabejohnson. You may change my mind. 😃
sanctuary-js/sanctuary-def#164 contains related discussion about what constitutes a record field.
Issue Analytics
- State:
- Created 6 years ago
- Comments:9 (9 by maintainers)
Top GitHub Comments
This is how we should describe string maps to people regardless of how we handle this edge case. My preference is to permit the rarely useful behaviour we’ve been discussing, but this doesn’t mean we need to advertise it. Every example of a string map in the documentation is currently an object literal, and I don’t see any reason for this to change.
The definition should be precise of course, but this could be followed by a less technical description:
A value of type
StrMap a
is an object whosetype
is'Object'
and whose enumerable properties are all members of typea
. A string map is a plain JavaScript object, such as{foo: 1, bar: 2, baz: 3}
, whose values are all members of some type (Number
, in this case).This issue has just become clearer in my mind. These properties should undoubtedly hold:
S.keys(S.concat(map, {})) = S.keys(map)
S.keys(S.concat({}, map)) = S.keys(map)
It’s not difficult to find a counterexample:
So, either
S.keys
needs to be updated to include enumerable inherited properties orZ.concat
andS.concat
need to be updated to ignore enumerable inherited properties. I far prefer the latter.At some point during the next few days I will open a pull request for this breaking change to
Z.concat
.