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

Implement object literals #25

Closed
masak opened this issue Oct 8, 2015 · 10 comments
Closed

Implement object literals #25

masak opened this issue Oct 8, 2015 · 10 comments

Comments

@masak
Copy link
Owner

masak commented Oct 8, 2015

Objects, if we get then in 007, will be immutable, just like arrays. (Implementing mutation isn't worth it for us, and having everything be immutable has some nice benefits, too.)

Here's what I'm proposing for syntax, based on ES6:

my obj = {
    prop1: 42,
    "quotes allow you to put a non-identifier as a property": "if that floats your goat",
    fn,                     # syntactic sugar for 'fn: fn'
    toString() {            # syntactic sugar for 'toString: sub () { ... }'
    },                      # trailing comma allowed
};

The run-time value would be a Val::Object.

I don't think there should be any JS-like this syntax/semantics. I think we should be running entirely on lexical scoping. That way, people would essentially get private attributes "for free". Since objects are immutable, this would be the only way to get immutability anyway.

In other words, there's no such thing as "object methods" (much like JavaScript), and there's no "object context"/this (unlike JavaScript). If you want to refer to the object, just assign it (like we did above with obj) and it'll be available lexically.

Accessing properties could probably borrow obj[key] — so we do array indexing and object property lookup with the same postfix operator. Or we go the Perl route on that one and have obj{key}. I'd be fine either way. I'd be fine with a getProp(obj, key) setting function too, I think. If we do the postfix lookup, we should consider also having the syntactic sugar obj.key (like JavaScript); that last one would only work if key is a valid identifier, of course.

I'd just like to add that I foresee people wanting to mutate their object, and we should probably supply a setProp(obj, key, newVal) setting function for that. Objects stay immutable, but it gets easier to derive new objects from old ones.

Also, there should be a keys(obj) setting function, returning an array of the key names of obj. This function gives no guarantees whatsoever about the order of the keys. There's no big need for a clone setting function, because objects are immutable.

Things we're explicitly not adding/borrowing from ES6 and other languages:

  • Classes
  • Inheritance
  • __proto__ and prototypes
  • Computed properties
@masak
Copy link
Owner Author

masak commented Oct 8, 2015

Oh, and happily, since we've now gotten rid of code blocks as a syntactic thing, there would be no parsing ambiguity between code blocks and objects.

Although since we still have Q::Statement::Block, statement starting with { would still unambiguously be a block and not an object literal. (Just like in JavaScript.)

@masak
Copy link
Owner Author

masak commented Oct 10, 2015

To avoid users having to linearly scan an object to check whether an object has a given property, we should also provide a hasProp(obj, key).

Making a lookup on a property that does not exist on the object should be a lookup error, just like out-of-range array lookups, and just as it is in Python:

$ python3
>>> class C:
...   pass
... 
>>> C().foo
AttributeError: 'C' object has no attribute 'foo'

I briefly considered making an exception for type(obj.prop), so that obj.prop would fail in any position except inside of a type() call. Then people could write

if type(obj.m) == "function" { ... }

instead of

if hasProp(obj, "m") && type(obj.m) == "function" { ... }

But let's not do that. Weirdly, I think it would be helpful to have that shortcut, but it doesn't feel elegant, and it introduces a bit of magic that involves both the runtime (which would usually throw that property lookup error) and the parser (which would have to be involved in silencing the error, since the exception is syntactical in nature).

@masak
Copy link
Owner Author

masak commented Oct 10, 2015

Actually — it strikes me now — it would make a lot more sense to have getProp, setProp and hasProp as methods on the objects. We might even shorten them to get, set, and has in that case.

These methods would be provided as if inherited/linked from a general Object class/prototype. (Though 007 has no such mechanism.)

The question presents itself what would happen if the object literal a contained getProp (or get) method, or any of the other methods. I don't know. The two design choices seem to be (a) parser error, or (b) allow it and let the user override these at their own peril. I'm sorely tempted to go with (a) for simplicity, even though (b) is kind of suggested by the inheritance analogy.

@masak
Copy link
Owner Author

masak commented Oct 10, 2015

I'd just like to add that I foresee people wanting to mutate their object, and we should probably supply a setProp(obj, key, newVal) setting function for that. Objects stay immutable, but it gets easier to derive new objects from old ones.

Though maybe setProp is a willfully misguiding name for that thing, given that it doesn't set anything; it builds something new for you. Maybe calling it butWith or something similar sets clearer expectations about the contract of the function/method.

I also got to thinking whether we should allow setting properties that don't exist on the original objects. (I.e. should we allow non-overriding property writes?) On the face of it, that seems perfectly harmless, but I think conservatively forbidding it might make life easier for us once we get to typing and #33. Specifically, something like this:

my coords = { x: 0, y: 0 };

would implicitly type the variable coords to be of type { x: Int, y: Int }. If you then did coords = coords.butWith("z", 0) later, that would fail to typecheck.

So maybe we should simply disallow it from the start. Post-rationalizing, maybe it's good for the soul to predeclare all the properties you plan to change even in the original object literal.

@masak
Copy link
Owner Author

masak commented Oct 12, 2015

Though maybe setProp is a willfully misguiding name for that thing, given that it doesn't set anything; it builds something new for you. Maybe calling it butWith or something similar sets clearer expectations about the contract of the function/method.

The answer, in retrospect, is obvious: eschew the von Neumann bottleneck, avoid anemic domain models! Naturally, the obvious way to update properties is to use object literals!

I propose update (for overwriting existing properties) and extend (for adding new ones).

my o = { x: 0, y: 0 };
say(o.update({ x: 1, y: 2 }).update({ x: 3 }));   # { x: 3, y: 2 }
say(o.extend({ z: 0 }));                          # { x: 0, y: 0, z: 0 }

update will error if any properties don't exist on the original object, and extend will error if any do. The fact that you can't both update and extend with one method is a feature, because it makes you stop and think about what you're doing. (Also, the methods are easier to type separately.)

@vendethiel
Copy link
Collaborator

fd02a5b / #37

@vendethiel
Copy link
Collaborator

  • make sure property lookup works on objects constructed by object literals
  • make sure obj[key] lookup works on them, too
  • implement get, has, update, extend, and id properties
  • implement the Q::Identifier { ... } syntax
  • make sure it works on all the Q types
  • make sure Object { ... } works
  • remove the function constructors from the built-in functions
  • update the tutorial and web page

@masak
Copy link
Owner Author

masak commented Nov 13, 2015

The question presents itself what would happen if the object literal a contained getProp (or get) method, or any of the other methods. I don't know. The two design choices seem to be (a) parser error, or (b) allow it and let the user override these at their own peril. I'm sorely tempted to go with (a) for simplicity, even though (b) is kind of suggested by the inheritance analogy.

As I'm about to implement this, I'm now tempted towards (b) instead. 007 (just as Perl or Python) isn't particularly keen to forbid things that you might as well override for some reason:

$ perl6 bin/007 -e='my for = 7; say(for)'
7

Though sometimes the built-in thing wins because it's special-cased in the parser:

$ perl6 bin/007 -e='my None = 7; say(None)'
None

In the case of user-overridden properties, though, I think it makes the most sense for the user's version to win. Rhymes well with the tendency for late-binding in objects, too.

Also, it does seem we'll get some reluctant kind of inheritence, so it's easy to motivate from that angle, too.

@masak
Copy link
Owner Author

masak commented Nov 27, 2015

All the follow-up actions are now done. Closing.

vendethiel++ for original patch, and giving us objects just when it became interesting to have them for synthetic Qtrees. :)

@masak masak closed this as completed Nov 27, 2015
@masak
Copy link
Owner Author

masak commented Jun 24, 2021

Very belatedly, I now think that get, set, and has should maybe all live on Object Dict instead, as static methods. The hand-wavy reason is that they are "infrastructure" (with regards to interacting with an object), and mixing them with "regular properties" in the object itself is asking for trouble down the line.

Interestingly, there's a recent ES proposal which seems to make the same point, belatedly proposing Object.hasOwn(obj, prop) as a safer obj.hasOwnProperty(prop); for reasons see which post.

I take this general sentiment ("put reflective/meta stuff outside of the object-level stuff") to be the driving force behind Bracha/Ungar's ideas about mirrors (ACM link) — without claiming to understand that work in detail. Indeed, from the abstract of that paper: "Stratification: meta-level facilities must be separated from base-level functionality".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants