4 ms·
I hear you. I don't like method chaining either. Instead of window.FindCanvasForObject(obj).Color(RED).MoveTo(10,30).LineTo(50,20). ... I much prefer:
by beagle3 10y ago
I hear you. I don't like method chaining either. Instead of
window.FindCanvasForObject(obj).Color(RED).MoveTo(10,30).LineTo(50,20). ...
I much prefer:
o = window.FindCanvasForObject(obj)
o.Color(RED);
o.MoveTo(10,30);
o.LineTo(50,20);
(Where, in C++ I would enclose this inside a { curly block } to give o the right type and make it local, and in Python I would add a "del o" at the end; and yes, I reuse 'o' to mean 'object' in these cases as much as possible, occasionally having "ox" and "oy" when I have x and y objects to deal with simultaneously).
I think it provides the fluency of method chaining with the readability of "standard" code, but this style seems to get scorn from both the chaining-loving people and the chaining-hating people. Oh well, different strokes etc.
- nostrademons 10y agoHow about: window .FindCanvasForObject(obj) .Color(RED) .MoveTo(10, 30) .LineTo(50, 20) This avoids the repetition of the local variable name, while still making it clear what's being called on the object.
- golergka 10y agoBut on what object? The fact that calling .MoveTo(10, 30) would return the same object it has been called on, and not, for example, a handle to created movement animation, is not exactly obvious. Modern pattern of methods which just return the objects that they've been called on, by default seems to be very trendy, but I fail to see how is it helpful.
- nostrademons 10y agoSame way that you use any other API: by reading the docs. This is at least a very common pattern; it's highly likely that whoever maintains this code will have seen it before. And if you're using an IDE, the IDE will even tell you this as you hit '.' BTW, it doesn't have to be the same object: if memory is abundant and CPU is not, it can often make sense to create a fresh object with each call. Doing this lets you share partially-constructed objects without worrying that mutation will result in surprising effects: let box = NewCanvas() .DrawLine(50, 0) .DrawLine(50, 50) .DrawLine(0, 50) .DrawLine(0, 0) let scene = [ box.Fill(Color.RED).TranslateTo(200,300), box.Fill(Color.BLUE).TranslateTo(100, 300), box.Fill(Color.GREEN).TranslateTo(400, 500) ] scene.forEach(box => box.Draw())
- golergka 10y ago> Same way that you use any other API: by reading the docs. Best APIs are written in such an obvious way that you don't need to read the docs to read the code and understand what's happening.
- nostrademons 10y agoThat doesn't apply to any of the code examples posted so far in this thread: they would all require documentation about side-effects, shared state, immediate vs. delayed effects, etc.
- julian_t 10y agoEr... on the window, as the indentation and API tells you. It may be "not exactly obvious" to you, but having been doing GUI programming for a long time, this makes complete sense to me. It is also, of course, a matter of personal style and preference. (Aside: some friends tell me, a vi user, that the way Emacs works is obvious. I disagree, but then, I don't use it). And as for "modern"... I've been using this since C++ gained references.
- golergka 10y agoAre you sure? Because to me it looks that first call should return a canvas, and all consecutive calls operate on it.
- Stratoscope 10y agoThe indentation misled you here. The first method call returns a child canvas object that the other methods act on. See my comment nearby in this thread for an alternative way to indent that avoids this confusion.
- golergka 10y agoI think it just proves my point. Do we really want indentation to be that critical to making code readable?
- Stratoscope 10y agoThat particular chain breaks a rule that I find very helpful: when you see a vertical column of leading dots, those methods should all be acting on the same object. When you introduce a child object, indent another level. So I would format that like this: window .FindCanvasForObject(obj) .Color(RED) .MoveTo(10, 30) .LineTo(50, 20) Or more compactly: window.FindCanvasForObject(obj) .Color(RED) .MoveTo(10, 30) .LineTo(50, 20)
- Groxx 10y agoJust to nitpick, those aren't quite equivalent. The second should be: o = window.FindCanvasForObject(obj); o = o.Color(RED); o = o.MoveTo(10,30); o = o.LineTo(50,20); Which leaves more room for error in the general case - if the API gives you a new object each time, and you fail to assign it every time (or say you have multiple and get the letters mixed up, or you do this rarely and forget), you lose some of the config. It can be fairly easily missed in code reviews. When it's all chained together, there's only one possible interpretation if it compiles and only has one assignment. But I can see where you're coming from. Chains can be abused rather horrifically. IMO part of that is because it's hard to make helper functions, e.g.: window.FindCanvasForObject(obj).Color(RED).MoveTo(10,30).LineTo(50,20) # plus def move_line(thing, start, end): return thing.MoveTo(start).LineTo(end) # leads to move_line(window.FindCanvasForObject(obj).Color(RED), (10, 30), (50, 20)) It breaks the straightforward left-to-right interpretation. So instead, people just make the chain longer and longer, forever. (not as much of a problem with a language with open classes, but modifying existing classes for ad-hoc helpers is usually frowned upon too.)
- beagle3 10y agoYou are correct, of course; the only API I used this with on JavaScript kept returning the original object (and that was documented behaviour) so it didn't matter. I most often use this with C++, where it is { CanvasObject *o = window.FindCanvasForObject(obj); o->Color(RED); o->MoveTo(10,30); o->LineTo(50,20); } Your point about helper functions is spot on. They break the form in C++ (and Python if you don't modify classes), but with this form it is still much better than breaking a fluent style line.
- maxxxxx 10y agoThis is much easier to debug. You can look at the state after each step.
- lacampbell 10y agoI disagree. You've created a variable for no reason. I hate having more variables than needed. I have to worry about things like its scope, its mutability, anywhere else it might be used, etc etc. It's pointless to me. Like another poster below, I do agree you should spread that out over a few lines though: window .FindCanvasForObject(obj) .Color(RED) .MoveTo(10,30) .LineTo(50,20)