digitalmars.D - Required Reading: "How Non-Member Functions Improve Encapsulation"
- Walter Bright (5/5) Oct 25 2017 for core D devs.
- rikki cattermole (21/30) Oct 25 2017 UFCS kills off a good part of those arguments, but point still stands.
- who cares (7/12) Oct 25 2017 I tend to agree. When a member function can be written with only
- Jacob Carlborg (6/11) Oct 26 2017 In D, protection attributes applies to the module. So if the free
- Kagamin (23/26) Oct 26 2017 You mean non-member functions are preferred? I encountered this
- Jacob Carlborg (4/10) Oct 27 2017 Adding methods to a struct will not increase its size.
- Kagamin (4/12) Oct 27 2017 Instance methods require this be passed by reference, which
- Jacob Carlborg (4/7) Oct 27 2017 Ah, right.
- w0rp (11/11) Oct 29 2017 I've noticed the benefits of writing non member functions in
- Jonathan M Davis (7/13) Oct 29 2017 Yeah, making functions generic can be a big win. The bigger question is ...
- Steven Schveighoffer (24/33) Oct 26 2017 I'm pretty sure I read that before.
- Dukc (10/30) Oct 27 2017 Assuming you don't want to change the original struct, this can
- JN (6/11) Oct 26 2017 As a counterpoint. I guess UFCS makes it less of a problem, but
- Jonathan M Davis (38/53) Oct 26 2017 At a previous job, we had our own string class which wrapped std::string
- Walter Bright (15/25) Oct 26 2017 The point is that functions that do not need access to private fields sh...
- Jonathan M Davis (57/82) Oct 26 2017 I agree that it's a good rule of thumb to aim for member functions to be...
- Kagamin (5/11) Oct 27 2017 Because it's functionality of the class. If it's not available,
- codephantom (18/27) Oct 31 2017 I'd like to do this too:
- Dmitry Olshansky (7/12) Oct 26 2017 The irony is that D’s private is “public to anyone in this
- H. S. Teoh (32/40) Oct 30 2017 Page 2 of this article is essentially another reason why UFCS in D
- Steven Schveighoffer (7/10) Oct 30 2017 You're missing a key piece here, in that anotherMethod does not ensure
- Jonathan M Davis (21/30) Oct 30 2017 Yeah, UFCS helps this whole concept, but the way that private works in D
- H. S. Teoh (64/95) Oct 30 2017 Yeah, the whole "private is module-private, not aggregate-private"
- codephantom (10/13) Oct 30 2017 I don't like it.
- Steven Schveighoffer (4/22) Oct 30 2017 I once thought as you do (though not as the syntax you propose). I now
- codephantom (11/14) Oct 30 2017 Yeah. I do like UFCS ... I was convinved after seeing Ali talk
- Steven Schveighoffer (5/24) Oct 30 2017 Except... then you can't use generic code with UFCS.
- codephantom (11/15) Oct 30 2017 Mmm... it sounds complex ;-)
- Jonathan M Davis (13/35) Oct 30 2017 Honestly, I think that generic code is the _only_ technical advantage of
- Patrick Schluter (5/29) Oct 30 2017 And under the hood the difference is also minimal. A member
- Dave Jones (4/14) Oct 31 2017 You dont need to know how its implemented. There's no benefit to
- H. S. Teoh (36/48) Oct 31 2017 The point is, you *shouldn't have to care*. If I use a library L that
- codephantom (18/21) Oct 31 2017 Yes, this is my main concern I guess, as I use pretty plain
- H. S. Teoh (17/31) Nov 01 2017 I'm a vim user, and frankly, I don't find the need for "enhanced"
- Dukc (13/22) Oct 31 2017 Yes, the idea here is great. It will work with range functions
- H. S. Teoh (83/108) Oct 31 2017 Haha, I knew somebody would bring this up. I don't have a good solution
- jmh530 (7/15) Oct 31 2017 Have you ever heard of the difference in how private works in D
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 2017
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 2017
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 2017
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 2017
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/184401197You 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 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 2017
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 2017
On Friday, 27 October 2017 at 07:36:45 UTC, Jacob Carlborg wrote:On 2017-10-26 12:42, 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.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.
Oct 27 2017
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 2017
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 2017
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 2017
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 2017
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 2017
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 2017
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: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 Davisfor 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 :)
Oct 26 2017
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 2017
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: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 DavisAs 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 2017
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 2017
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 2017
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 2017
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 2017
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 2017
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: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 DavisPage 2 of this article is essentially another reason why UFCS in D totally rawkz. In D, we can take Scott's advice *without* sufferingfrom 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.
Oct 30 2017
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: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. [...]On 10/30/17 1:40 PM, H. S. Teoh wrote: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.Page 2 of this article is essentially another reason why UFCS in D totally rawkz. In D, we can take Scott's advice *without* sufferingfrom 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.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 2017
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 2017
On 10/30/17 9:44 PM, codephantom wrote:On Monday, 30 October 2017 at 23:03:12 UTC, H. S. Teoh wrote:I once thought as you do (though not as the syntax you propose). I now embrace UFCS fully, it's awesome. -SteveBut 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 2017
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. -SteveYeah. 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 2017
On 10/30/17 9:59 PM, codephantom wrote:On Tuesday, 31 October 2017 at 01:47:39 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. -SteveI once thought as you do (though not as the syntax you propose). I now embrace UFCS fully, it's awesome. -SteveYeah. 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 2017
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. -SteveMmm... 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 2017
On Monday, October 30, 2017 22:10:26 Steven Schveighoffer via Digitalmars-d wrote:On 10/30/17 9:59 PM, codephantom wrote: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 DavisOn Tuesday, 31 October 2017 at 01:47:39 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.I once thought as you do (though not as the syntax you propose). I now embrace UFCS fully, it's awesome. -SteveYeah. 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 2017
On Tuesday, 31 October 2017 at 01:47:39 UTC, Steven Schveighoffer wrote:On 10/30/17 9:44 PM, codephantom wrote: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.On Monday, 30 October 2017 at 23:03:12 UTC, H. S. Teoh wrote:I once thought as you do (though not as the syntax you propose). I now embrace UFCS fully, it's awesome.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 2017
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: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.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.
Oct 31 2017
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: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 -- Государство делает вид, что платит нам зарплату, а мы делаем вид, что работаем.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.
Oct 31 2017
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 2017
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 2017
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 2017
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: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 WhearFor 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 2017
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 2017