Skip to content

Instantly share code, notes, and snippets.

@tj
Created November 2, 2011 20:13
Embed
What would you like to do?
@tj
Copy link
Author

tj commented Nov 2, 2011

soooo, my thoughts on proxies & similar features (op overloading etc). Admittedly you can do some fun/weird/interesting things with them, but I think that's where it usually ends. Every language or library has some degree of magic, but more is not necessarily a good thing. I can only speak for myself but the more I've learned the less I appreciate this sort of functionality, visual & functional ambiguity only slows things down.

Take for example foo in Ruby, this could be many things: a method call, a local var, method_missing delegation, and the list goes on. Different people will have different opinions on that, but for me that's a really bad thing, I dont want to come back to my code and spend time wondering where this thing came from, let alone a library I'm using or contributing to. Things like foo() or self.foo() are not ambiguous, increasing clarity, though I suppose you have to type slightly more.

The only "real" use-case I've heard for proxies/accessors is for a js DOM implementation, which is fine, but why not take out the accessors from the DOM? do they really benefit anyone? I would much prefer window.location('foo'), window.location() => { ... }.

The approach Lua takes is far more acceptable IMHO, sure it's still a lot of magic, but it's a tightly integrated core concept and feels far less tacked on.

@mythz
Copy link

mythz commented Nov 2, 2011

Proxies are awesome, they allow UI binding, dynamic-style message dispatch, etc. It will replace the ugly tacked-on hacks that libraries like SproutCore and Knockout.js have to resort to.
Dynamic message passing is Obj-C major strength (and effectively what makes it bearable).

Amongst its many use-cases is UI Bindings and Sevice Proxies, but it can benefit any solution that decouples sender from receiver.

@braddunbar
Copy link

I feel similarly and I'm learning today that others do too (at least @jashkenas and yourself). I'm very worried about getters/setters gaining widespread adoption on the web. I know they're implemented in modern browsers already, but no one uses them in code that needs to be cross browser since they won't work everywhere (like Array::forEach and friends).

I think they're harmful for the same reasons goto is harmful. They hurt my ability to reason about code. In the following I can't reason about the time complexity of foo because the property access could do literally anything an extra function call could do. Language features should make it easier for me to reason about simple code, not harder.

function foo(object){
  return object.bar;
}

And as for DOM implementations, I think we all agree that the weird semantics of host objects are bad for the web. So why are we attempting to emulate them rather than change them? Someone please enlighten me.

@tj
Copy link
Author

tj commented Nov 2, 2011

@braddunbar the canvas API is a good example too. ctx.fillStyle= like really? haha why, im sure people are fine with ctx.fillStyle(val). We ran into an issue with this recently as we needed to intercept calls to all CanvasRenderingContext2d methods, but good luck doing that with the accessors. I love the API other than that brutal choice.

@mikeal
Copy link

mikeal commented Nov 2, 2011

@mythz what ugly tacked on hacks? please give some specific examples.

My main worry with Proxies is that someone writes an Interface library and other people think that it's a good idea. In Python, meta classes opened the door to interfaces (implemented by zope.interface) which is the source of Python's largest warts (Zope and Twisted).

We have something that feels like idiomatic JavaScript. Most popular libraries today feel similar, Proxies open the door to APIs that fall outside of that idiom and the vast majority of Hamony proposals themselves feel like they fall outside of that idiom.

That idiom might not be for everybody, but there are plenty of languages for those people, you can't be everything to everybody.

@braddunbar
Copy link

@visionmedia That's a great example. Javascript's function-centric design gives it an _amazing_ ability to proxy/mutate/patch/intercept/augment any (function based) api. getters/setters seem to just throw that away. And for what?

@mikeal
Copy link

mikeal commented Nov 2, 2011

@visionmedia that's because the W3C doesn't understand JavaScript and don't write specs necessarily with JavaScript in mind.

I had a long argument with Jonas Sicking from Mozilla about WebIDL. WebIDL is an interface description language for new browser standards which basically creates a language for writing a DOM or Canvas spec. Basically, it's a huge barrier between web developers and participation in standards. It also allows, and encourages to some extent, more standards that can't be implemented in pure js without Proxies or Host Objects. From his point of view it's a step forward, because it finally gives some definition to the language that vendors share when developing a specification, but from my point of view it's just a wall between the W3C and the web developer community.

@tj
Copy link
Author

tj commented Nov 2, 2011

Yeah that's interesting, definitely seems like a backwards approach to fix a problem

@mythz
Copy link

mythz commented Nov 2, 2011

@mikeal

//Knockout.js today without proxies
var viewModel = {
    firstName: ko.observable("Bert"),
    lastName: ko.observable("Bertington")
};
viewModel.fullName = ko.dependentObservable(function() {
    return this.firstName() + " " + this.lastName();
}, viewModel);
ko.applyBindings(viewModel);


//What it could look like with Proxies
var viewModel = UIBinding.create(el, { 
    firstName: "Bert", 
    lastName: "Bertington", 
    fullName: function(){ this.firstName + " " + this.lastName; }
});

The viewModel via proxy is much more powerful as it can intercept any action you take on it and provide any behavior it wants, i.e. you cold easily have:

viewModel.save(); 

Which it could post those changes to the server, reload the new server modified entity and update the UI.

@braddunbar
Copy link

@mythz I've worked with knockout fairly extensively, and I agree that it's api can be pretty ugly. However, I don't think that an ugly api is cause for adding a new language feature. In my opinion Backbone accomplishes much the same thing as knockout but in a much nicer way. In no small part due to it's pub/sub api, which I think makes for much more idiomatic javascript.

@tj
Copy link
Author

tj commented Nov 2, 2011

You get small syntactic benefits with Backbone or similar, user.name = 'tj' vs the user.set('name', 'tj') or whatever is there currently but I agree with @braddunbar that it's largely an API design issue. "cute" APIs seem to be the major thing to benefit from proxies, ORMs etc

@mythz
Copy link

mythz commented Nov 2, 2011

It's an API issue in that you can only provide the best API that works, not one that is desired/natural. Which is what Proxies allow.

@mikeal
Copy link

mikeal commented Nov 2, 2011

@mythz the code you posted is easily accomplished, today, writing a custom create method. Nothing in your ideal example requires proxies.

@mikeal
Copy link

mikeal commented Nov 2, 2011

You're right, it's an API issue, which means it's the difference between and idiomatic JavaScript API and a "desired" API that is not idiomatic and looks more like some other language.

@mythz
Copy link

mythz commented Nov 2, 2011

@mikeal

Exactly how is:

user.name = 'tj';

able to update the UI without proxies?

@tj
Copy link
Author

tj commented Nov 2, 2011

just dont do that. IMHO a property assignment should be a property assignment, no side-effects

@mythz
Copy link

mythz commented Nov 2, 2011

Right, so

document.getElementById('theid').value = 'shouldn't update the UI?';

@tj
Copy link
Author

tj commented Nov 2, 2011

nope, the DOM is brutal, what's wrong with getElementById('whatever').value('foo')?

@braddunbar
Copy link

@mythz Right. Host objects don't play by the rules and we should be trying to change that, not changing the rules to compensate.

@tj
Copy link
Author

tj commented Nov 2, 2011

conceptually I suppose it would be more elegant if obj.foo = bar was merely sugar for obj.set('foo', bar), then you intercept those however you like, but there's obviously performance issues tied to "features" like that

@mikeal
Copy link

mikeal commented Nov 2, 2011

@mythz your example didn't have an assignment like the one you're mentioning now.

@braddunbar
Copy link

The problem with that is you can't tell the difference (lexically). The syntactic niceness isn't worth the lost ability to reason about your code.

@mikeal
Copy link

mikeal commented Nov 2, 2011

it's also conceptually simpler to know that assignment always mean the same thing and that their behavior isn't in some abstract method somewhere. having all this customization is not without cost, the semantics need to be kept in your head whenever you're reading code and all of a sudden the simplest thing, property assignment, can mean a whole host of other things.

@mythz
Copy link

mythz commented Nov 2, 2011

@mikeal

The point of my example was to show the necessary code required to proxy your model with a UI element so that:

viewModel.firstName = "new name"; 

Can update the UI, or update the server, or anything else.

@tj
Copy link
Author

tj commented Nov 2, 2011

@mikeal yeah exactly, at least with method calls you're opting in to complexity

@tj
Copy link
Author

tj commented Nov 2, 2011

it's not even like it's more convenient really:

user.set('name', 'tj');
user.name('tj'); // if you know ahead of time via schema etc
user.name = 'tj';

not a huge deal

@mythz
Copy link

mythz commented Nov 2, 2011

Except no one expects user.name to be a function.

@paulmillr
Copy link

Here's an example of Proxies awesomeness: Clojure-like contracts, http://disnetdev.com/contracts.coffee/. In production mode, you can just disable proxies without neediness to rewrite the code, so contracts overhead wouldn't affect users.

@tj
Copy link
Author

tj commented Nov 4, 2011

that's a reasonable use-case I guess. The fact that you kinda have to use js in the browser makes that ok, but that's not the language js is... otherwise you can just have your pick of functional/strictly-typed languages. The fact that you would have to compile that to js first to achieve the functionality is even worse, at that point you might as well just make it a transpiler feature that you can toggle on and off

@andreyvit
Copy link

@visionmedia What makes the language better for your use cases (and aesthetic sense, which I must add I highly respect) isn't the same thing as what makes the language better for the popular and important use cases. You don't have to use getters or proxies if you don't want to, but I'm surprised that you cannot see why some other peoples' code would be improved by using them.

  1. If magic get/set is the prevalent library abstraction you use, there's really no possible confusion about assignment semantics. Imagine most property assignments in your code being function calls instead; that's losing a lot of clarity without adding any knowledge. When working in a reactive environment, a magic get and a magic set are the normal behavior of every property assignment
  2. That's not some niche use case. People are building stuff like reactive environments and data binding in JavaScript today, taking it to the places it has never been before, and we have no idea what will be popular tomorrow. We want to support that, rather than discourage that.
  3. Most importantly, the argument you're making is essentially counter to the idea of writing code in terms of interfaces, not implementations. Sometimes the distinction between .name and .name() is an implementation detail that should be hidden to allow proper duck typing. Should a function called max really care if it's operating on a real array or a reactive array class? While it's possible to over-abstract things, the right amount of abstraction isn't always no abstraction.
  4. Then there's an issue of being able to make changes. One day your person.name is just a property, the next day you want it to be reactive, so it has to become person.name(). Using different syntax to access immutable and mutable properties is crazy, you'd have to either always use person.get('name') just in case, or remember if name is mutable or immutable today. I'm all for refactoring and find-and-replace, but this particular trivial change feels like a stupid chore and gets annoying fast.
  5. And there's evidence of existing demand and of people applying ugly workarounds. Isn't that the best indication that a language feature is desirable? (Honest question.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment