www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Required Reading: "How Non-Member Functions Improve Encapsulation"

reply Walter Bright <newshound2 digitalmars.com> writes:
for core D devs.

"How Non-Member Functions Improve Encapsulation" by Scott Meyers

http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197

Note that I'm as guilty as anyone for not understanding or following these 
guidelines. I expect we can do much better.
Oct 25
next sibling parent rikki cattermole <rikki cattermole.co.nz> writes:
On 25/10/2017 11:19 PM, Walter Bright wrote:
 for core D devs.
 
 "How Non-Member Functions Improve Encapsulation" by Scott Meyers
 
 http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197 
 
 
 Note that I'm as guilty as anyone for not understanding or following 
 these guidelines. I expect we can do much better.
UFCS kills off a good part of those arguments, but point still stands. ```D struct Point { private int[2] d; this(int x, int y) { d[0] = x; d[1] = y; } property { ref int x() { return d[0]; } ref int y() { return d[1]; } } } void main() { Point p = Point(1, 3); p.y = 2; } ``` Hehe ;)
Oct 25
prev sibling next sibling parent reply who cares <wsffws 12345678.cz> writes:
On Wednesday, 25 October 2017 at 22:19:23 UTC, Walter Bright 
wrote:
 for core D devs.

 "How Non-Member Functions Improve Encapsulation" by Scott Meyers

 http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197

 Note that I'm as guilty as anyone for not understanding or 
 following these guidelines. I expect we can do much better.
I tend to agree. When a member function can be written with only the public declarations (aka the "public API") it can be set as a free function. However during my youth i've written lots of huge classes...now that i don't write much anymore it's too late to apply the rule :/
Oct 25
parent Jacob Carlborg <doob me.com> writes:
On 2017-10-26 08:56, who cares wrote:

 I tend to agree. When a member function can be written with only the 
 public declarations (aka the "public API") it can be set as a free 
 function. However during my youth i've written lots of huge 
 classes...now that i don't write much anymore it's too late to apply the 
 rule :/
In D, protection attributes applies to the module. So if the free functions are defined in the same module, it's easy to accidentally access private data. -- /Jacob Carlborg
Oct 26
prev sibling next sibling parent reply Kagamin <spam here.lot> writes:
On Wednesday, 25 October 2017 at 22:19:23 UTC, Walter Bright 
wrote:
 for core D devs.

 "How Non-Member Functions Improve Encapsulation" by Scott Meyers

 http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197
You mean non-member functions are preferred? I encountered this more from performance point: especially in case of small structures like Point it would be more beneficial to pass them by value than by reference, which can be achieved by extension methods, but then you need to import the respective module to have those extension methods available. It would work more palatable if extension methods from the structure's module were available without requiring import. It may rely on static declarations inside the struct like C# does it: struct Size { int width,height; alias add=.add; //? } Size add(Size s1, Size s2) { return Size(s1.width+s2.width, s1.height+s2.height); } or just work without it. Such alias would allow to provide extension methods from other modules, though this would mean circular dependency between modules.
Oct 26
parent reply Jacob Carlborg <doob me.com> writes:
On 2017-10-26 12:42, Kagamin wrote:

 You mean non-member functions are preferred? I encountered this more 
 from performance point: especially in case of small structures like 
 Point it would be more beneficial to pass them by value than by 
 reference, which can be achieved by extension methods, but then you need 
 to import the respective module to have those extension methods 
 available.
Adding methods to a struct will not increase its size. -- /Jacob Carlborg
Oct 27
parent reply Kagamin <spam here.lot> writes:
On Friday, 27 October 2017 at 07:36:45 UTC, Jacob Carlborg wrote:
 On 2017-10-26 12:42, Kagamin wrote:

 You mean non-member functions are preferred? I encountered 
 this more from performance point: especially in case of small 
 structures like Point it would be more beneficial to pass them 
 by value than by reference, which can be achieved by extension 
 methods, but then you need to import the respective module to 
 have those extension methods available.
Adding methods to a struct will not increase its size.
Instance methods require this be passed by reference, which requires storage fiddling on the caller side. It's likely to disappear after inlining, but still.
Oct 27
parent reply Jacob Carlborg <doob me.com> writes:
On 2017-10-27 11:06, Kagamin wrote:

 Instance methods require this be passed by reference, which requires 
 storage fiddling on the caller side. It's likely to disappear after 
 inlining, but still.
Ah, right. -- /Jacob Carlborg
Oct 27
parent reply w0rp <devw0rp gmail.com> writes:
I've noticed the benefits of writing non member functions in 
Python codebases. Say if you have a User model in a Django ORM, 
and you have a Thing model, and some operation on User and Thing. 
I've noticed that your code is almost always better if you write 
a non member function on User and Thing, instead of a member of 
User or Thing.

Often a function belongs to neither type. Instead the logic cuts 
across those two types. The key disadvantage I notice is ending 
up with very large and unreadable classes which poorly categorise 
business logic, when you could have been categorising functions 
in modules based on different business needs.
Oct 29
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Sunday, October 29, 2017 08:45:15 w0rp via Digitalmars-d wrote:
 I've noticed the benefits of writing non member functions in
 Python codebases. Say if you have a User model in a Django ORM,
 and you have a Thing model, and some operation on User and Thing.
 I've noticed that your code is almost always better if you write
 a non member function on User and Thing, instead of a member of
 User or Thing.
Yeah, making functions generic can be a big win. The bigger question is what to do when it doesn't really make sense to make the function generic, and it doesn't need access to the private members of the type that it would always be used with. In that case, it doesn't need to be a member function, but it could be. - Jonathan M Davis
Oct 29
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 10/25/17 6:19 PM, Walter Bright wrote:
 for core D devs.
 
 "How Non-Member Functions Improve Encapsulation" by Scott Meyers
 
 http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197 
 
 
 Note that I'm as guilty as anyone for not understanding or following 
 these guidelines. I expect we can do much better.
I'm pretty sure I read that before. However, D's lookup rules fail miserably when it comes to templates: mod1.d: auto callFoo(T)(T t) { return t.foo; } mod2.d: struct S { int x; } int foo(S s) { return s.x * 5; } void main() { auto s = S(1); assert(s.foo == 5); assert(s.callFoo == 5); // can't compile } Would be nice to have a way around this. Not sure what it would look like. Also in D, it's harder to make this help for encapsulation since the non-member functions would have to be in another module. -Steve
Oct 26
parent Dukc <ajieskola gmail.com> writes:
On Thursday, 26 October 2017 at 12:19:33 UTC, Steven 
Schveighoffer wrote:
 D's lookup rules fail miserably when it comes to templates:

 mod1.d:

 auto callFoo(T)(T t)
 {
   return t.foo;
 }

 mod2.d:

 struct S
 {
    int x;
 }

 int foo(S s) { return s.x * 5; }

 void main()
 {
    auto s = S(1);
    assert(s.foo == 5);
    assert(s.callFoo == 5); // can't compile
 }

 Would be nice to have a way around this. Not sure what it would 
 look like.
Assuming you don't want to change the original struct, this can be worked around by making a wrapper type using alias this. I think that's logical because you have to be explicit about which foreign functions you want the imported algorithm to see. There would be a function hijacking problem otherwise. What is the catch here, is that alias this won't solve cases where the free function takes a reference to the wrapped type or returns it.
Oct 27
prev sibling next sibling parent reply JN <666total wp.pl> writes:
On Wednesday, 25 October 2017 at 22:19:23 UTC, Walter Bright 
wrote:
 for core D devs.

 "How Non-Member Functions Improve Encapsulation" by Scott Meyers

 http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197

 Note that I'm as guilty as anyone for not understanding or 
 following these guidelines. I expect we can do much better.
As a counterpoint. I guess UFCS makes it less of a problem, but it's nice when using an IDE to just type foo. , press ctrl-space and see a nice list of methods that can be used with the class. When having free functions, you don't get to enjoy that :)
Oct 26
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Thursday, October 26, 2017 12:53:38 JN via Digitalmars-d wrote:
 On Wednesday, 25 October 2017 at 22:19:23 UTC, Walter Bright

 wrote:
 for core D devs.

 "How Non-Member Functions Improve Encapsulation" by Scott Meyers

 http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/1844
 01197

 Note that I'm as guilty as anyone for not understanding or
 following these guidelines. I expect we can do much better.
As a counterpoint. I guess UFCS makes it less of a problem, but it's nice when using an IDE to just type foo. , press ctrl-space and see a nice list of methods that can be used with the class. When having free functions, you don't get to enjoy that :)
At a previous job, we had our own string class which wrapped std::string just because some of the developers wanted our extra string functions on the type where they would be easy to find (even just for documentation purposes). They were of the opinion that once you separate the functions from the class, your project will ultimately end up with multiple header files of functions doing similar things, because at least some of the time, developers wouldn't know about the existing functions and would add their own own. And with sufficiently large projects, that does seem to happen all too often. I still don't agree with the idea that putting stuff on the class is better just because it makes stuff easier to find, but I can see why someone would think that. As has been pointed out elsewhere in this thread, the encapsulation benefits don't exist in the same way in D unless you put the free functions in separate modules, and then you have to import other stuff to use them, whereas in C++, you can just put the free functions next to the type and still get the encapsulation benefits. So, the benefit of splitting the functions out within a module is fairly minimal in D. Rather, the main reason I see for splitting functions out is in order to make them more generic, and I think that the push to do that in D has a tendency to make a lot of functions free functions that might have been member functions in most other languages. And in D, there's the issue that declaring a function to be used with UFCS instead of as a member function can be a bit of a pain when the type is templated. You have to repeat a bunch of stuff that's just part of the templated struct or class. You end up with additional templates and template constraints (much of which is duplicated) just to separate the functions from the type. That being said, the time that I'm most likely to turn a member function into a free function when the function isn't going to be made generic is when the type is templated simply because then I can put the unittest block right next to the function without having it be part of the template (since that's almost always the wrong thing to do if you don't do some boilerplate with static ifs to make it so that they're only compiled into a single instantiation that's intended just for testing). Overall, I think that the idea that functions should be free functions where reasonable is good advice, but it needs to be taken with a grain of salt. - Jonathan M Davis
Oct 26
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 10/26/2017 3:05 PM, Jonathan M Davis wrote:
 As has been pointed out elsewhere in this thread, the encapsulation benefits
 don't exist in the same way in D unless you put the free functions in
 separate modules, and then you have to import other stuff to use them,
 whereas in C++, you can just put the free functions next to the type and
 still get the encapsulation benefits. So, the benefit of splitting the
 functions out within a module is fairly minimal in D. Rather, the main
 reason I see for splitting functions out is in order to make them more
 generic, and I think that the push to do that in D has a tendency to make a
 lot of functions free functions that might have been member functions in
 most other languages.
The point is that functions that do not need access to private fields should NOT be part of the class. Most of the discussion here is based on the idea that those functions should still be semantically part of the class, even if not physically. That idea should be revisited. Why should they be semantically part of the class? You can also do things like: --- s.d ------- struct S { int x; ref int X() { return x; } } --- splus.d ---- public import s; int increment(S s) { s.X() += 1; } // note no access to S.x --- user.d ---- import splus; void foo(ref S s) { s.increment(); }
Oct 26
next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Thursday, October 26, 2017 16:29:24 Walter Bright via Digitalmars-d 
wrote:
 On 10/26/2017 3:05 PM, Jonathan M Davis wrote:
 As has been pointed out elsewhere in this thread, the encapsulation
 benefits don't exist in the same way in D unless you put the free
 functions in separate modules, and then you have to import other stuff
 to use them, whereas in C++, you can just put the free functions next
 to the type and still get the encapsulation benefits. So, the benefit
 of splitting the functions out within a module is fairly minimal in D.
 Rather, the main reason I see for splitting functions out is in order
 to make them more generic, and I think that the push to do that in D
 has a tendency to make a lot of functions free functions that might
 have been member functions in most other languages.
The point is that functions that do not need access to private fields should NOT be part of the class. Most of the discussion here is based on the idea that those functions should still be semantically part of the class, even if not physically. That idea should be revisited. Why should they be semantically part of the class? You can also do things like: --- s.d ------- struct S { int x; ref int X() { return x; } } --- splus.d ---- public import s; int increment(S s) { s.X() += 1; } // note no access to S.x --- user.d ---- import splus; void foo(ref S s) { s.increment(); }
I agree that it's a good rule of thumb to aim for member functions to be the set of functions that require access to private members, but I've never felt that it's useful to be pedantic about it (particularly in D, where you don't even get encapsulation if they're in the same module). When I write a struct or class, it's generally designed with a set of operations that conceptually go with it, and I don't see whether they need to access to private members as being all that relevant to that. Functions which aren't really conceptually part of the class or struct certainly should be separate, but for me at least, it's about the API and what it's conceptually trying to do, and what the type is trying to be and represent - what its abstraction is. And in some cases, separating a function from a type just because it doesn't happen to use any private members then feels like an artificial separation. On some level, I think that it comes down to a question of what operations are part of the abstraction and what operations are just using the abstraction, if it's part of the abstraction, IMHO, it just makes more sense for it to be a member function regardless of whether it accesses private members. Obviously, that's subjective, and there's then disagreement at least some of the time on what should and shouldn't be a member function, but I don't think that it's necessarily the case that having everything that doesn't need access to private members as free functions leads to the abstraction that a struct or class presents being very clean. And once you get into classes and inheritance, breaking things up based on what needs access to private members definitely falls apart in a number of circumstances, because then you're very clearly abstracting behaviors (which can then be overidden) as opposed to simply encapsulating data and operating on it like some structs do. I just tend to think of types in general that way, not just those involving interfaces and inheritance. If you're coming at the type from the standpoint that everything that is conceptually part of the type is actually part of the type, and everything else is separate, then on some level, that will follow the idea that functions that access private members are member functions and those that don't aren't, but at least some of the time, it won't. And in that case, I'm generally going to make it a member function. I think that the problem is when folks just add functions to a type when they don't need to be member functions to do what they do and really aren't conceptually part of the type. They're just put on there because it's easy or because that's what folks are used to having to do in languages like Java or because it works better with auto-completion (which honestly, I think is one area where IDEs make things worse; having auto-completion is fine, but writing code in a certain way because of auto-completion is an anti-pattern IMHO). Every time that a programmer is adding a function, they really should be considering whether it really should be a member function or whether it makes more sense for it to be a free function. So, I think that the advice that member functions should generally be the set of functions that need access to private members is good think to about and something that programmers should keep in mind, but I also don't think that that's really the best dividing line in general as to what should and shouldn't be a member function. And thanks to how access levels work in D, it's even less useful as a goal than it would be in C++, because structs and classes simply aren't encapsulated in the same way, even if programmers don't normally write code which breaks the encapsulation of structs and classes unless they need to for the same reasons that you'd write a friend function in C++. - Jonathan M Davis
Oct 26
prev sibling next sibling parent Kagamin <spam here.lot> writes:
On Thursday, 26 October 2017 at 23:29:24 UTC, Walter Bright wrote:
 The point is that functions that do not need access to private 
 fields should NOT be part of the class. Most of the discussion 
 here is based on the idea that those functions should still be 
 semantically part of the class, even if not physically. That 
 idea should be revisited. Why should they be semantically part 
 of the class?
Because it's functionality of the class. If it's not available, it will be reimplemented and duplicated. C++ doesn't have such problem, because in a way all imports are public there so you have no chance to separate a function from type.
Oct 27
prev sibling parent codephantom <me noyb.com> writes:
On Thursday, 26 October 2017 at 23:29:24 UTC, Walter Bright wrote:
 You can also do things like:

 --- s.d -------
 struct S { int x; ref int X() { return x; } }

 --- splus.d ----
 public import s;
 int increment(S s) { s.X() += 1; } // note no access to S.x

 --- user.d ----
 import splus;
 void foo(ref S s) { s.increment(); }
I'd like to do this too: ------------------------- import std.stdio; void main() { //int foo; if (int foo.bar != 0) // no, sorry, you cannot declare foo here. { throw new Exception("foo.bar != 0"); } } auto bar(int x,) { return -10; } -----------------------------------------
Oct 31
prev sibling next sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On Wednesday, 25 October 2017 at 22:19:23 UTC, Walter Bright 
wrote:
 for core D devs.

 "How Non-Member Functions Improve Encapsulation" by Scott Meyers

 http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197

 Note that I'm as guilty as anyone for not understanding or 
 following these guidelines. I expect we can do much better.
The irony is that D’s private is “public to anyone in this module”. With that in mind free function or not you don’t get anything. Splitting it off to another module is clunky and I’d use it only for functions that may work for different types (template).
Oct 26
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Oct 25, 2017 at 03:19:23PM -0700, Walter Bright via Digitalmars-d wrote:
 for core D devs.
 
 "How Non-Member Functions Improve Encapsulation" by Scott Meyers
 
 http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197
 
 Note that I'm as guilty as anyone for not understanding or following
 these guidelines. I expect we can do much better.
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: // C++: class C { public: int method(); private: ... }; int anotherMethod(C &c, ...); C c; c.method(); anotherMethod(c); // <-- syntactic inconsistency // D: class C { public int method(); private: ... } int anotherMethod(C c, ...); C c; c.method(); c.anotherMethod(); // <-- Uniform syntax FTW! Arguably, this means in D encapsulation is even better than in C++: the user doesn't even have to care whether a function is a member or not. The same syntax does the Right Thing(tm). Furthermore, if the class implementation changes in a drastic way that makes it possible to make a current member function a non-member, we can do it in D without needing to touch any client code at all! T -- Кто везде - тот нигде.
Oct 30
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
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. -Steve
Oct 30
next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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. I expect that that logic is mainly the justification for not implementing friend as opposed to the actual reason, but it does pretty much fly in the face of the idea that you need to be so worried about encapsulation that you use free functions to prevent a function from accidentally using a private member. 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. - Jonathan M Davis
Oct 30
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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 -- Тише едешь, дальше будешь.
Oct 30
next sibling parent reply codephantom <me noyb.com> writes:
On Monday, 30 October 2017 at 23:03:12 UTC, H. S. Teoh wrote:
 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.
I don't like it. When I see obj.func(), to me, func() is a member function. Why should I spend any time trying to work out whether it's a member function or a free function? It doesn't make sense to me. If it's really a free function, I'd like that to be more explicit.. e..g obj.\func(). (or something like that ..where \ means its a free function)
Oct 30
next sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 10/30/17 9:44 PM, codephantom wrote:
 On Monday, 30 October 2017 at 23:03:12 UTC, H. S. Teoh wrote:
 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.
I don't like it. When I see obj.func(), to me, func() is a member function. Why should I spend any time trying to work out whether it's a member function or a free function? It doesn't make sense to me. If it's really a free function, I'd like that to be more explicit.. e..g obj.\func().   (or something like that ..where \ means its a free function)
I once thought as you do (though not as the syntax you propose). I now embrace UFCS fully, it's awesome. -Steve
Oct 30
next sibling parent reply codephantom <me noyb.com> writes:
On Tuesday, 31 October 2017 at 01:47:39 UTC, Steven Schveighoffer 
wrote:
 I once thought as you do (though not as the syntax you 
 propose). I now embrace UFCS fully, it's awesome.

 -Steve
Yeah. I do like UFCS ... I was convinved after seeing Ali talk about it: https://www.youtube.com/watch?v=vYEKEIpM2zo What a great ambassador for D he is! But being able to differentiate whether a line of code is calling a method of an object, or a free function... that's the area that concerns me. I'm lazy.. I would like such code to be more explicit....and not leave it to me to work out what's going on.
Oct 30
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 10/30/17 9:59 PM, codephantom wrote:
 On Tuesday, 31 October 2017 at 01:47:39 UTC, Steven Schveighoffer wrote:
 I once thought as you do (though not as the syntax you propose). I now 
 embrace UFCS fully, it's awesome.

 -Steve
Yeah. I do like UFCS ... I was convinved after seeing Ali talk about it: https://www.youtube.com/watch?v=vYEKEIpM2zo What a great ambassador for D he is! But being able to differentiate whether a line of code is calling a method of an object, or a free function... that's the area that concerns me. I'm lazy.. I would like such code to be more explicit....and not leave it to me to work out what's going on.
Except... then you can't use generic code with UFCS. For example, arrays couldn't be ranges, because you can't just do arr.front, you'd have to do arr.\front. -Steve
Oct 30
next sibling parent codephantom <me noyb.com> writes:
On Tuesday, 31 October 2017 at 02:10:26 UTC, Steven Schveighoffer 
wrote:
 Except... then you can't use generic code with UFCS.

 For example, arrays couldn't be ranges, because you can't just 
 do arr.front, you'd have to do arr.\front.

 -Steve
Mmm... it sounds complex ;-) to make it worse, I can apparently call func() with the parenthesis: obj.func; now, is func an attribute of obj? is it a method of obj? is it a free function? these syntactic abstractions are leaky...we need to encapsulate intent too ;-)
Oct 30
prev sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Monday, October 30, 2017 22:10:26 Steven Schveighoffer via Digitalmars-d 
wrote:
 On 10/30/17 9:59 PM, codephantom wrote:
 On Tuesday, 31 October 2017 at 01:47:39 UTC, Steven Schveighoffer wrote:
 I once thought as you do (though not as the syntax you propose). I now
 embrace UFCS fully, it's awesome.

 -Steve
Yeah. I do like UFCS ... I was convinved after seeing Ali talk about it: https://www.youtube.com/watch?v=vYEKEIpM2zo What a great ambassador for D he is! But being able to differentiate whether a line of code is calling a method of an object, or a free function... that's the area that concerns me. I'm lazy.. I would like such code to be more explicit....and not leave it to me to work out what's going on.
Except... then you can't use generic code with UFCS. For example, arrays couldn't be ranges, because you can't just do arr.front, you'd have to do arr.\front.
Honestly, I think that generic code is the _only_ technical advantage of UFCS - everything else is just subjective preference about syntax. But the fact that you can call a function without worrying about whether it's a member function or a free function is huge for generic code - especially if you want a type to be able to provide an optimized version of a function (e.g. implementing its own find if it can implement find more efficiently than the default linear implementation). Without UFCS, it gets way more complicated to be able to call a function that might be a member function or a free function, and in likelihood, the result would be that code would always use one or the other rather than supporting both. - Jonathan M Davis
Oct 30
prev sibling parent Patrick Schluter <Patrick.Schluter bbox.fr> writes:
On Tuesday, 31 October 2017 at 01:47:39 UTC, Steven Schveighoffer 
wrote:
 On 10/30/17 9:44 PM, codephantom wrote:
 On Monday, 30 October 2017 at 23:03:12 UTC, H. S. Teoh wrote:
 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.
I don't like it. When I see obj.func(), to me, func() is a member function. Why should I spend any time trying to work out whether it's a member function or a free function? It doesn't make sense to me. If it's really a free function, I'd like that to be more explicit.. e..g obj.\func().   (or something like that ..where \ means its a free function)
I once thought as you do (though not as the syntax you propose). I now embrace UFCS fully, it's awesome.
And under the hood the difference is also minimal. A member function is compiled to a free function in the object file. It's only the mangled name that makes the difference.
Oct 30
prev sibling next sibling parent Dave Jones <dave jones.com> writes:
On Tuesday, 31 October 2017 at 01:44:58 UTC, codephantom wrote:
 On Monday, 30 October 2017 at 23:03:12 UTC, H. S. Teoh wrote:
 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.
I don't like it. When I see obj.func(), to me, func() is a member function. Why should I spend any time trying to work out whether it's a member function or a free function? It doesn't make sense to me.
You dont need to know how its implemented. There's no benefit to knowing. It's a waste of time / distraction to even think about it.
Oct 31
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Oct 31, 2017 at 01:44:58AM +0000, codephantom via Digitalmars-d wrote:
 On Monday, 30 October 2017 at 23:03:12 UTC, H. S. Teoh wrote:
 
 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.
 
I don't like it. When I see obj.func(), to me, func() is a member function. Why should I spend any time trying to work out whether it's a member function or a free function? It doesn't make sense to me.
The point is, you *shouldn't have to care*. If I use a library L that provides some type T, and the library provides operation X that I can perform on type T, all I care about is that variables of type T can have X performed on it. Is X a member function? A free function? Who cares! That's just an implementation detail. As the user of library L, how T and X are implemented are none of my business. X can be a remote procedure call to a network service for all I care -- my code doesn't have to know. All it needs to know is that type T can have operation X performed on it. That's the whole point of encapsulation. You *don't have to know* how something is implemented. In fact, it's better that you don't, because later when you upgrade library L, and X changes from member function to free function, or vice versa, *your code doesn't have to change at all*. That's why we care about encapsulation in the first place. Why should *I* have to change my code just because the upstream authors of library L decides that X is better implemented as a free function vs. a member function, or vice versa? I don't care, and I shouldn't have to care. Having the same interface (i.e., UFCS syntax) to call X regardless of how it's implemented is a big advantage, because it frees me, the downstream user, from needing to care about fiddly details of library L's implementation. Instead of upgrading library L and then having to spend 10 hours fixing all my function calls to X, I can just upgrade library L and let the compiler figure out which calls are member functions and which are free functions. My code doesn't have to change one bit. Let the machine do the grunt work, free up the human to do the higher level stuff. The one case where the difference matters is when you're trying to debug something. In that case, I'd say the onus is really upon the debugger to tell you what kind of function it was. Surely the debugger must have this information; it's just a matter of conveying the information to the user adequately. If current tools don't allow you to do this easily, that's a problem with the tools, not with the concept of encapsulation. T -- Государство делает вид, что платит нам зарплату, а мы делаем вид, что работаем.
Oct 31
parent reply codephantom <me noyb.com> writes:
On Tuesday, 31 October 2017 at 15:45:42 UTC, H. S. Teoh wrote:
 The one case where the difference matters is when you're trying 
 to debug something.  In that case, I'd say the onus is really 
 upon the debugger to tell you what kind of function it was.
Yes, this is my main concern I guess, as I use pretty plain editors that tell me nothing. I rely on the code to tell me what I need to know. foo.bar(); foo.\bar(); // where \ means a free function A different syntax for calling free functions would certainly make it clearer (as the above demonstrates), but as you argue, it would have a negative effect on encapsulation. I guess with a more enhanced editor I could just mouse over UFCS syntax, and it could identify a free function from a member function. That would be nice, since there's no other way to know without exploring code elsewhere... I guess the days of use a plain text editor...are slowly coming to and end ;-( ..what a shame...as I only just recently 'upgraded' from using vi to using micro.... https://github.com/zyedidia/micro
Oct 31
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Nov 01, 2017 at 03:38:32AM +0000, codephantom via Digitalmars-d wrote:
 On Tuesday, 31 October 2017 at 15:45:42 UTC, H. S. Teoh wrote:
 The one case where the difference matters is when you're trying to
 debug something.  In that case, I'd say the onus is really upon the
 debugger to tell you what kind of function it was.
Yes, this is my main concern I guess, as I use pretty plain editors that tell me nothing. I rely on the code to tell me what I need to know.
[...]
 I guess with a more enhanced editor I could just mouse over UFCS
 syntax, and it could identify a free function from a member function.
 That would be nice, since there's no other way to know without
 exploring code elsewhere...
I'm a vim user, and frankly, I don't find the need for "enhanced" editors at all. (I don't even use syntax highlighting, but that's another story. :P) If there's a function call that could be either a member function or a UFCS free function, all I need is to have the debugger print a stacktrace and that ought to clear things up. It will even resolve other issues like telling me exactly which overload is being called, if there are complicated overload sets, and point me to the exact file/line of the code. At that point, the difference between UFCS free function or member function is basically irrelevant.
 I guess the days of use a plain text editor...are slowly coming to and
 end ;-(
[...] Nope. Plain text editors still rule. GUIs are for wimps. :-P T -- The best way to destroy a cause is to defend it poorly.
Nov 01
prev sibling next sibling parent reply Dukc <ajieskola gmail.com> writes:
On Monday, 30 October 2017 at 23:03:12 UTC, H. S. Teoh wrote:
 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.
Yes, the idea here is great. It will work with range functions you define. The problem is, it won't work with Phobos functions because they do not see your extensions to that proprietary type. They see only the true member functions of it. Unless you hack phobos and add your extensions to that propietary library there, which would be a very ugly solution. You can of course wrap that proprietary range type but that is a lot of manual work and requires maintenance. Alias this solves some cases but not all of them. I am not sure what would be the best way for the language to handle this but for sure not the present way. The idiom is otherwise so great.
Oct 31
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Oct 31, 2017 at 08:45:11AM +0000, Dukc via Digitalmars-d wrote:
 On Monday, 30 October 2017 at 23:03:12 UTC, H. S. Teoh wrote:
 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.
Yes, the idea here is great. It will work with range functions you define. The problem is, it won't work with Phobos functions because they do not see your extensions to that proprietary type. They see only the true member functions of it. Unless you hack phobos and add your extensions to that propietary library there, which would be a very ugly solution. You can of course wrap that proprietary range type but that is a lot of manual work and requires maintenance. Alias this solves some cases but not all of them. I am not sure what would be the best way for the language to handle this but for sure not the present way. The idiom is otherwise so great.
Haha, I knew somebody would bring this up. I don't have a good solution to this either. The problem is that you can't export the type to Phobos along with the additional UFCS stuff you tacked on to it. I think Phobos itself contains several hacks in order to work around this problem for basic types, like importing std.array in generic code that doesn't actually reference any arrays, but the import is necessary so that arrays retain their range API. Similar problems arise when you pass user-defined types to Phobos where some methods are implemented as UFCS. The basic problem is that when a generic function in Phobos looks up a method of type T, the lookup is done *in the scope of the Phobos module*, not the caller's scope that passed in the T in the first place. For example: /* usertype.d */ module usertype; struct UserType { int memberMethod(Args...)(Args args); } /* ufcs.d */ module ufcs; import usertype; int ufcsMethod(Args...)(UserType u, Args args); /* main.d */ module main; import usertype, ufcs; void main() { UserType u; int x, y, z; // Lookup happens in module main, function main. Since // usertype.UserType is visible here, .memberMethod // resolves to usertype.UserType.memberMethod. u.memberMethod(x, y, z); // Lookup happens in module main, function main. Since // ufcs.ufcsMethod is visible here, .ufcsMethod resolves // to ufcs.memberMethod. u.ufcsMethod(x, y, z); // Calls Phobos function u.find(x); } /* snippet of std.algorithm */ module std.algorithm; ... HayStack find(HayStack, Needle)(HayStack h, Needle n) { ... // Lookup happens in module std.algorithm. Since we // don't have the import of module ufcs here, // .ufcsMethod cannot be resolved. h.ufcsMethod(n); ... } I wonder if there's an easy way to extend the language so that you can specify which scope a function lookup will happen in. Suppose, hypothetically, we can specify a lookup to happen in the caller's context. Then UFCS would work: /* snippet of std.algorithm */ module std.algorithm; ... HayStack find(HayStack, Needle)(HayStack h, Needle n) { ... // Hypothetical syntax: with (HayStack.__callerContext) h.ufcsMethod(n); // Now lookup happens in the caller's context, i.e., // module main, function main. Since module ufcs is // visible there, this call resolves to ufcs.ufcsMethod. ... } The problem with this is that allowing the callee to access the context of the caller opens up a can of worms w.r.t. symbol hijacking and accessing local variables in the caller, which should be illegal. Seems like the only workable solution in the current language is to wrap the type in a custom type. But like you said, that's high-maintenance, and decreases encapsulation because when the implementation of UserType changes, the wrapper type needs to change accordingly. And the current implementation of alias this is not without its own set of problems. There *is* a generic wrapper type in Phobos that uses opDispatch for member forwarding, but last time I checked, it also comes with its own set of issues. So currently, we still don't have a perfect solution for this, even though we're so tantalizingly close. T -- Unix is my IDE. -- Justin Whear
Oct 31
prev sibling parent jmh530 <john.michael.hall gmail.com> writes:
On Monday, 30 October 2017 at 23:03:12 UTC, H. S. Teoh wrote:
 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.
 
Have you ever heard of the difference in how private works in D vs. C++ ever causing any problems when calling D code from C++, or vice-versa? I imagine if there is an issue with calling C++ code from D, you can do what you say wrt submodules, but I'm not sure it really matters or not.
Oct 31