On Mon, Oct 30, 2017 at 02:05:51PM -0600, Jonathan M Davis via Digitalmars-d wrote: > On Monday, October 30, 2017 14:18:56 Steven Schveighoffer via Digitalmars-d > wrote: > > On 10/30/17 1:40 PM, H. S. Teoh wrote: > > > Page 2 of this article is essentially another reason why UFCS in D > > > totally rawkz. In D, we can take Scott's advice *without* > > > suffering > > > > > from syntactic inconsistency between member and non-member > > > functions: > > You're missing a key piece here, in that anotherMethod does not > > ensure the encapsulation of C. It can call 'method' just fine. > > > > Yes, it's great that UFCS can help with encapsulation via external > > methods, but it's going to be difficult to prevent access to private > > data, you have to use 2 modules. Not a very clean solution IMO. > > Yeah, UFCS helps this whole concept, but the way that private works in > D means that compiler-enforced encapsulation simply doesn't happen at > the type level. > > Another thing to think about is that private is private to the module > rather than the class or struct partly on the theory that you should > be able to keep track of everything in the module and maintain it > appropriately so that things like whether the type is fully > encapsulated aren't really an issue.
Yeah, the whole "private is module-private, not aggregate-private" throws a monkey wrench into the works. I can understand the logic behind module-private vs. aggregate-private, but sometimes you really *do* want aggregate-private, but D doesn't let you express that except via splitting things up into submodules, which is a lot of overhead for minor payback. So my examples would have to involve submodules in order to really prove the point. [...] > Still, UFCS does mean that it's less jarring to make something a free > a function, and it then fits well with the cultural push we have to > make functions generic, in which case, they're generally going to be > free functions. But the gains from making something generic are clear, > whereas the encapsulation gains of using a free function with UFCS are > far less substantial, if they exist at all. [...] There's definitely gain here, IMO. Part of the idea of encapsulation is the independence of client code from implementation details, and one such implementation detail is whether or not a function is implemented as a class member or a free function. Ideally user code shouldn't need to care about the difference. In C++, however, user code has no choice, because you can't write obj.func() when func is not a member. But in D, UFCS allows obj.func() to work for both member functions and free functions, so if the client code uses the obj.func() syntax, it won't have to care about the difference. One may argue about whether allowing arbitrary extensions to a class / aggregate via UFCS is necessarily a good thing; but IMO, there are definite benefits to it. One classic example is std.range using UFCS to essentially extend built-in arrays to have a range API, by providing free functions .empty, .front, .popFront that take array arguments. Of course, whether this is a good thing can be argued for or against, but I see this as just one example of a wider pattern of *adaptability*, which IMO is a very good thing. For example, suppose you're using a proprietary library that provides a class X that behaves pretty closely to a range, but doesn't quite have a range API. (Or any other API, really.) Well, that's not a problem, you just write free functions that forward to class X's methods to bridge the API gap, and off you go. You don't have to work with your upstream provider, who may not be able to provide a fix until months later, and you don't have to create all sorts of wrapper types just to adapt one API to another. And if done right, if a new library version is released which breaks an old API, say a method xyz() is deprecated and removed, or renamed, or whatever, all you have to do is to write a free function named xyz, that forwards to the new method(s), and you don't have to do a massive upgrade of your entire codebase. Another win for encapsulation. (And yes, ideally the upstream library wouldn't break backward compatibility, but we all know that in the real world it does happen every now and then. And when it does, the last thing I want to be worrying about is renaming 1500+ function calls to use xyz(p,q,r) syntax instead of p.xyz(q,r) syntax. In C++, I'd have no choice, but in D, UFCS lets my code become agnostic to this distinction, which is a very good thing IMO.) And as Scott already mentions, you can spice up the API you were given with more convenience functions that do frequently-performed method call sequences. Carried one step further, this means if you have two classes, presumably coming from two different vendors, with similar but not-quite-the-same APIs, UFCS allows me to extend the two APIs so that they converge. Then my code can freely use objects from either library without needing to care about API differences between them. Now, *that's* encapsulation! Basically, the more my code can become independent of API changes, the better. According to Scott's definition, that's an increase in encapsulation, because the number of changes required to update my codebase in the face of an API change is greatly reduced. UFCS gives me some pretty powerful tools in this regard. T -- Тише едешь, дальше будешь.
