Closed jamesonquinn closed 13 years ago
Neat idea, but I'm afraid it's a bit too special-case for it's own syntax, and is entirely possible through defining a helper function. For previous tickets about this, see: #1141, and #881.
I think that this proposal is significantly lighter and more general-purpose than the others you linked to. coco does this (as I realized only after making this proposal) using a.b!c
. That syntax is obviously terser than the one I've proposed, but by the same token it is less clear and harder-to-search-the-docs-for. Still, I think that the fact that two people independently came up with this proposal, and both used some syntax including !, suggests that it is intuitive.
If by "too special-case", you mean that not enough use-cases have been shown, I'm sure that could be resolved.... :)
Yes -- real world use cases are always the best motivation when arguing for a feature. If this is only really useful for defining the namespace at the top of the file, then eh... But if you can show a bunch of real-world code that's clarified by adding it, that's compelling.
Basically, this helps when refactoring related code into several subfiles. I'm working on a web app, and so most of my files are putting various things in places like window.myapp.models, window.myapp.views, etc. I'd like to be able to split any one of these files in two or move code from one to another without worrying about loading order or annoying extra checks for existence of those sub-namespaces. Currently I'm using code like ((windows.myapp ?= {}).models ?= {}).MyModel = MyModel
but that is definitely less-readable than my suggestion.
As to the specific syntax involved, I'm not too attached to my specific suggestion.
Instead of including all that noise in every file -- it's much nicer to have your namespaces built-out in your first JS file, no? We do:
// Provide top-level namespaces for our javascript.
(function() {
window.dc = {};
dc.controllers = {};
dc.model = {};
dc.app = {};
dc.ui = {};
})();
... and then never have to worry about it.
Sure, that's one way to do it. But you wanted real-world use cases, I gave one. My way is more flexible in terms of being able to mix and match js files without needing to always remember to include that "first js file" at the top. My way also means there's no "first js file" for various developers to collide in on source control. It's a matter of preference.
I can also imagine using this kind of thing if I were doing monkey-patching-like addition of attributes to an object that wasn't created locally. I'd want to be sure I set up a namespace only the first time I "patched" an object, and this would make that (even more) trivial (that is, a couple of characters, instead of one line.)
Right -- and what I was saying before is that module namespaces is the only use-case that's ever cited when talking about this feature. Just that use-case isn't quite enough to justify adding special syntax for, and can already be covered quite well by a helper function.
toucher =
touch: (obj) ->
obj._toucher?!.touched = true
abuse: (obj) ->
obj._toucher?!.abused = true
is_touched: (obj) ->
obj._toucher?.touched?
untouch: (obj) ->
delete obj._toucher
Obviously, it's a silly example, but I can certainly imagine real-world cases analagous to the above.
Another real-world example:
Patching backbone.js:
_add: (model, options) ->
super model, options
if model.resource_uri
(this._byurl ?= {})[model.resource_uri] = model
+1. I assign nested properties to objects on the fly frequently.
For example, I might want to assign
chart.margin.left = 12
before I've established that chart has a "margin" property. Because some use cases need no chart.margin property at all, it becomes inelegant to define chart = { margin: {} } in a "namespace" as @jashkenas recommends above, especially since I nest several deep properties this way. Imagine:
obj[id][category][attr] = value
is currently a major pain but could become painless (and beautiful) as
obj[id]![category]![attr] = value
or, as @jamesonquinn suggests:
obj[id]?![category]?![attr] = value
This seems philosophically similar to RDBMS (define all structure at the beginning) vs NoSQL (define arbitrarily deep hierarchies on the fly), and it is hard to deny the momentum and benefits of the latter. Am I missing something?
edit: moved operators out of key, per comment below.
should not be in the key imho
(chart.margin ?= {}).left = 12
See the other issues where we agree that that is sufficiently succinct while also sufficiently versatile.
@michaelficarra Can you demonstrate the same example using square bracket notation rather than dot notation? In my use case the property names, 'margin' and 'left' would not be known, but passed as vars.
Just use bracket notation. (chart[prop] ?= {}).foo = 1
Ok got it, thanks. So the example from above currently looks like this:
((obj[id] ?= {})[category] ?= {})[attr] = value
which I can live with. Not that much more verbose (at least at two-levels deep) than
obj[id]?![category]?![attr] = value
Dynamically expandable objects? That would be a great use case for Proxies IMO :smiley:
@sxv, if you're doing this a lot maybe a helper function can help:
nestedAssign = (obj, props..., lastProp, value) ->
baseObj = obj
for prop in props
baseObj = (baseObj[prop] ?= {})
# Maybe you can throw an error here if typeof baseObj isnt 'object'
baseObj[lastProp] = value
obj
Then you can use it as:
nestedAssign {}, 'foo', 'bar', 'baz', 42 # -> {foo:{bar:{baz:42}}}
Or, in your case:
nestedAssign obj, id, category, attr, value
+1.
I think @sxv's !
suggestion is clearly superior to any purposed work-around. It's concise, communicative, and expressive.
I don't understand the arguments against this feature. Why is this 'establishment operator' more specific and less useful than the 'existential operator'? It seems if one is good so is the other. Are we saying this is a bad pattern or something?
Notice how the discussion doesn't talk about problem domains, specific implementations, or even specific types. How is this specific? Further, if we think about how this would be implemented, it's not hard nor impractical.
This what the existential operator does:
[when chaining] if all of the properties exist then you'll get the expected result, if the chain is broken, undefined is returned instead of the TypeError that would be raised otherwise.
This is what the establishment operator would do:
[when chaining] if all the properties exist then you'll get the expected result, if the chain is broken, objects are created to prevent the ReferenceError that would be raised otherwise.
Seems generic, clear, and symmetric. Shouldn't we reopen this?
After sleeping on it I take this back. I think the best argument is that it is a bad pattern. If we can't guarantee that an object exists that uncertainty ripples through the rest of our code. Much better to explicitly create it in one spot (like in @jashkenas namespace example).
I would love to see this implemented as either foo?!bar
or foo!bar
(second format makes much more sense to me though). My use case, is building an object to send to server, which updates a sparse object with only the keys I have populated.
If I want to add an item to the customer entity, I don't want to have to deal with the entire customer object which is shared between different parts of the app, I simply want to do...
customer!hat!types = ['bowler', 'trilby']
ajax.put resturi, customer
Rather than...
customer = customer or {}
((customer ?= {}).hat ?= {}).types = ['bowler', 'trilby']
ajax.put resturi, customer
Gets better the more nesting that is involved.
It is not really relevant to generate an empty customer object in my case, but I have many nested properties that I need to generate.
I agree that (semi) autovivification is nice.
Currently I can write:
a ?= 1
But if I write
a.b?.c ?= 1
anda.b
is undefined, then nothing happens.I'd like to propose a ?!. operator, so that
a.b?!.c
is equivalent to(a.b ?= {}).c
. It would be useful for namespace-like things, where you don't want to worry about who defines it first.