-
-
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 prop function #161
Add prop function #161
Conversation
It's not clear to me which is the most appropriate membership check. I can, though, provide what I believe is a compelling example in favour of // Pair :: (a, b) -> Pair a b
function Pair(fst, snd) {
this[0] = fst;
this[1] = snd;
}
Pair.prototype.length = 2; Should Also, the usual traps with inherited properties affect object-as-dictionary rather than object-as-record. When using an object to keep track of claimed usernames, |
return obj[key]; | ||
} else { | ||
throw new TypeError('‘prop’ expected object to have a property named ' + | ||
'‘' + key + '’; ' + R.toString(obj) + ' does not'); |
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.
Let's indent this line to align it with the first '
on the preceding line.
So kind of a more type-dangerous get? Might be nice to mention Should we include |
We don't use |
Ya, I was thinking something along those lines.
ha! |
You make a convincing case for function(x) { return x != null && R.contains(_type(x), ['Array', 'Function', 'Object', 'String']); } I still have no idea why I can't use |
What's the motivation for doing so?
There are two different concepts in JavaScript referred to as "hoisting": When one writes… (function() {
console.log(x);
var x = 42;
}());
(function() {
var x;
console.log(x);
x = 42;
}()); This will log
We could do so by defining var prop =
def(…); then assigning |
Because |
Thanks for the explanation, Stefano! I now understand the motivation. :)
Primitive values other than > true.a
undefined
> 'foo'.toUpperCase()
'FOO' So Accessible isn't quite right, in this case. We could remove the constraint and determine whether the second argument is valid in the function body. This would enable consistent error messages: S.prop('x', null);
// ! No ‘x’ property in null
S.prop('x', {});
// ! No ‘x’ property in {} |
Ah yes, through the magic of autoboxing. Although something like
Sounds good, I should be able to work on this later on today. |
… or |
Ha. That's a new one, how does that work? |
|
This implementation got really hairy in the end. To support |
I don't see a reason to support We should be able to use the - if (typeof obj === 'string') {
- /*jshint -W053 */
- obj = new String(obj);
- /*jshint +W053 */
- }
- if (obj != null && typeof obj !== 'number' &&
- typeof obj !== 'boolean' && key in obj) {
+ if (obj != null && key in Object(obj)) { |
Ah yes, that's much better. ⚡ |
Actually, can we remove the |
Wouldn't that send us back here? |
//# prop :: Accessible a => String -> a -> b | ||
//. | ||
//. Takes a property name and returns the value of that property from the | ||
//. specified object; otherwise throws an error. |
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 this?
Takes a property name and a record (an object with known fields of known types), and returns the value of the specified field. If for some reason the record lacks the specified field, a type error is thrown.
For accessing properties of uncertain objects, use get
instead.
We should add a "see also" link to the description of get
.
Not if we do something like this: var boxed = Object(obj);
if (key in boxed) {
return boxed[key];
... |
Ah, I see what you're saying. Sounds great to me! |
This is shaping up a lot better now. ⚡ |
//. | ||
//. Takes a property name and an object and returns the value of the | ||
//. specified field. If for some reason the record lacks the specified field, | ||
//. a type error is thrown. |
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 suggest two tweaks:
- "object with known properties" in place of "object"; and
- "property" in place of "field" (since we no longer mention records).
return boxed[key]; | ||
} else { | ||
throw new TypeError('‘prop’ expected object to have a property ' + | ||
'named ‘' + key + '’; ' + R.toString(obj) + ' does not'); |
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.
When wrapping, I like things to align. Let's use one of the following forms:
throw new TypeError('...' +
'...' +
'...');
throw new TypeError(
'...' +
'...'
);
Haha! With |
Actually, that's a good point. |
eq(S.prop('0', [1, 2, 3]), 1); | ||
eq(S.prop('length', 'abc'), 3); | ||
eq(S.prop('x', Object.create({x: 1, y: 2})), 1); | ||
}); |
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.
Let's include a test for an inherited property. For example:
eq(S.prop('global', /x/g), true);
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.
⚡
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, does Object.create
not fulfil that already? x
and y
live on the created object's prototype.
console.log(Object.create({x: 1, y: 1}));
// => {}
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.
The example I suggested is still useful, but it doesn't test what I was hoping to test since global
also exists as an instance property:
> Object.prototype.hasOwnProperty.call(/x/g, 'global')
true
Although, does
Object.create
not fulfil that already?x
andy
live on the created object's prototype.
Good point! I didn't realize that Object.create
worked this way. 👍
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.
Yeah it's not obvious, that's why I wrote the test the way I did initially with an object named proto
that I then passed into create
.
5622161
to
f14847b
Compare
Is there any more that needs to be done on this? |
No, but I'd like to do a few other things before merging this pull request:
I plan to take care of these things this evening. :) |
Actually, I did my tax return this evening instead. :\ |
Haha. Rather you than me! |
Could you rebase this branch, Stefano? |
Yes, of course. ⚡ |
You'll need to update the expected error messages. ;) While you're at it, you can take advantage of the |
Yup, on it! |
assert.throws(function() { S.prop('xxx', [1, 2, 3]); }, | ||
errorEq(TypeError, | ||
'‘prop’ expected object to have a property ' + | ||
'named ‘xxx’; [1, 2, 3] does not')); |
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.
Let's use throws
rather than assert.throws
for these assertions as well.
Also, now that we're not enforcing a maximum line length let's not wrap the messages. :)
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.
⚡
🌳 Thank you for persevering with this one, Stefano! |
A surprisingly tricky one! |
As discussed in #159. I've a few reservations about this, firstly if I define
S.prop
asvar prop = S.prop = def(...
then I get lots of errors in the tests when I replace instances ofR.prop
inindex.js
:Another thing is that
Accessible
is a very permissive type for this function, it allows you to pass numbers or booleans in even though they don't support property access so you end up getting error messages that leak implementation details to the user:Also, I think it's strange that a function that operates on
Records
should consider keys that are present in the prototype chain. What are people's thoughts?