digitalmars.D - Do non-member functions improve encapsulation in D?
- Lars T. Kyllingstad (25/25) Apr 20 2014 In his article "How non-member functions improve encapsulation"
- monarch_dodra (14/39) Apr 20 2014 One thing to keep in mind, is that with the module system, and
- Lars T. Kyllingstad (6/14) Apr 20 2014 Very true. This reminds me of another difference between D and
- Rikki Cattermole (22/47) Apr 20 2014 My rules that I try to adhere to are:
- Andrej Mitrovic via Digitalmars-d (3/6) Apr 20 2014 TDPL actually references this IIRC.
- Gary Willoughby (15/40) Apr 20 2014 This is a quote from Walter that sums the reasoning up perfectly:
- Lars T. Kyllingstad (8/19) Apr 20 2014 However, in D, all functions defined in the same module as a
- monarch_dodra (13/28) Apr 20 2014 Wouldn't this be "worked around" by packages?
- Gary Willoughby (5/10) Apr 20 2014 That may be true but doesn't detract anything from the quote and
- Gary Willoughby (19/23) Apr 20 2014 Yeah it does. If the function can be used generically across many
- Lars T. Kyllingstad (26/39) Apr 21 2014 I agree. If a function is generally useful outside the context
- Gary Willoughby (45/65) Apr 21 2014 In this case i would say go with the method and not a non-member
- "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= (17/24) Apr 21 2014 I think this view is too simple. Even if a function is generally
- Lars T. Kyllingstad (8/23) Apr 21 2014 I agree, but I think that's more a question of *when* a function
- Andrei Alexandrescu (2/5) Apr 20 2014 No, only those in that module. There's no change. -- Andrei
- Lars T. Kyllingstad (19/26) Apr 21 2014 Ok, so "any number" was poorly phrased. What I meant was "a
- Andrei Alexandrescu (7/17) Apr 21 2014 The point here is boundedness, i.e. whether stuff that's affected is
- Steven Schveighoffer (9/26) Apr 21 2014 Sure, but Lars' point that it completely precludes the encapsulation
- Andrei Alexandrescu (2/9) Apr 21 2014 Got it, thanks. -- Andrei
- Jacob Carlborg (6/14) Apr 21 2014 Phobos is a very bad example of code organizing. I'm almost exclusively
- Dicebot (7/26) Apr 24 2014 I like it much more than class-based privacy for the simple
- Gary Willoughby (6/9) Apr 24 2014 That's what i've started to do in a recent project and then use
- Nick Treleaven (10/18) Apr 24 2014 I think this is a good argument. True private aids developers when
- Steven Schveighoffer (34/37) Apr 21 2014 First, you rightly destroy the main reason for making a module-level
- Lars T. Kyllingstad (30/57) Apr 21 2014 Based on this, combined with your points 6 and 3 further down --
- Steven Schveighoffer (40/55) Apr 21 2014 Demonstration:
- Lars T. Kyllingstad (18/62) Apr 21 2014 Wow, I didn't know that. I thought the most specialised function
- Lars T. Kyllingstad (4/5) Apr 21 2014 As in "it shouldn't be that way", not as in "I think you're wrong
- "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= (8/13) Apr 21 2014 I agree. What was the reasoning by conflating the two?
- Steven Schveighoffer (11/21) Apr 21 2014 I definitely restricted myself too much when I said "another type." Yes,...
- Andrej Mitrovic via Digitalmars-d (7/8) Apr 21 2014 Here's another one, the bug report is about enums but it showcases an
- Artur Skawina via Digitalmars-d (14/15) Apr 21 2014 [...]
- Meta (18/34) Apr 21 2014 Shouldn't this be possible if you want to make g a template
In his article "How non-member functions improve encapsulation" [1], Scott Meyers makes a good argument for why free functions should generally be preferred over class methods in C++. TL;DR: Fewer member functions means fewer functions that break when the class implementation changes, and free functions can be spread across different header files, allowing client code to only #include the ones that are needed. In D, the situation is somewhat different. Firstly, private symbols are accessible throughout the module within which the class/struct is defined, and secondly, UFC allows us to call free functions using the same syntax as member functions. In other words, *any* non-virtual function could in principle be written as a free function. The fact that "private" really means "module private" in D means that any number of functions can break when a class/struct implementation changes. So if we are to take Meyers' advice, we have to define a new module for each class/struct, and move its associated free functions to neighbouring modules. However, this would lead to a proliferation of modules that I doubt anyone would want. So, can anyone think of some good guidelines for when to make a function a member, when to write it as a free function in the same module, and when to move it to a different module? [1] http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197
Apr 20 2014
On Sunday, 20 April 2014 at 07:11:41 UTC, Lars T. Kyllingstad wrote:In his article "How non-member functions improve encapsulation" [1], Scott Meyers makes a good argument for why free functions should generally be preferred over class methods in C++. TL;DR: Fewer member functions means fewer functions that break when the class implementation changes, and free functions can be spread across different header files, allowing client code to only #include the ones that are needed. In D, the situation is somewhat different. Firstly, private symbols are accessible throughout the module within which the class/struct is defined, and secondly, UFC allows us to call free functions using the same syntax as member functions. In other words, *any* non-virtual function could in principle be written as a free function. The fact that "private" really means "module private" in D means that any number of functions can break when a class/struct implementation changes. So if we are to take Meyers' advice, we have to define a new module for each class/struct, and move its associated free functions to neighbouring modules. However, this would lead to a proliferation of modules that I doubt anyone would want. So, can anyone think of some good guidelines for when to make a function a member, when to write it as a free function in the same module, and when to move it to a different module? [1] http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197One thing to keep in mind, is that with the module system, and templates, is that free functions can only be called if the module *knows* about your free function. For example "int[]" is a range thanks to the free "front/popFront", but also *because* `std.range` imports `std.array`, and as such *knows* about them. If you tried the same thing yourself, with your user defined type, it wouldn't work. From there, while I'm inclined to say that "yes, non-member functions improve encapsulation", everything that "defines" an object *must* be member. Anything non-member is second class, and "helper".
Apr 20 2014
On Sunday, 20 April 2014 at 08:25:47 UTC, monarch_dodra wrote:One thing to keep in mind, is that with the module system, and templates, is that free functions can only be called if the module *knows* about your free function. For example "int[]" is a range thanks to the free "front/popFront", but also *because* `std.range` imports `std.array`, and as such *knows* about them. If you tried the same thing yourself, with your user defined type, it wouldn't work.Very true. This reminds me of another difference between D and C++, namely the fact that C++ supports ADL/Koenig lookup, while D doesn't. If it did, arrays would no longer be special in this way. I'm not sure whether the benefits of ADL outweigh the drawbacks, though.
Apr 20 2014
On Sunday, 20 April 2014 at 07:11:41 UTC, Lars T. Kyllingstad wrote:In his article "How non-member functions improve encapsulation" [1], Scott Meyers makes a good argument for why free functions should generally be preferred over class methods in C++. TL;DR: Fewer member functions means fewer functions that break when the class implementation changes, and free functions can be spread across different header files, allowing client code to only #include the ones that are needed. In D, the situation is somewhat different. Firstly, private symbols are accessible throughout the module within which the class/struct is defined, and secondly, UFC allows us to call free functions using the same syntax as member functions. In other words, *any* non-virtual function could in principle be written as a free function. The fact that "private" really means "module private" in D means that any number of functions can break when a class/struct implementation changes. So if we are to take Meyers' advice, we have to define a new module for each class/struct, and move its associated free functions to neighbouring modules. However, this would lead to a proliferation of modules that I doubt anyone would want. So, can anyone think of some good guidelines for when to make a function a member, when to write it as a free function in the same module, and when to move it to a different module? [1] http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197My rules that I try to adhere to are: * Modules do one thing. All members of a module meet said modules purpose. I.e. std.traits is a great example of this, but std.algorithm not quite so.. * Modules can have many free functions (I classify them as checks) but few classes/structs unless its a defs file in which case it can have quite a lot of them but few 'public' functions and a lot of 'checks' functions. * If it returns a new instance of a type and needs access to private class/struct property then member. * If it returns a new instance but doesn't need a types private property then free. * If it doesn't return a new instance of a type but may return another type then member. Generally speaking as long as you keep to the first (modules purpose) rule then they stay smallish. Which is to me the most important part. Definition of a checks function is that they do one thing. They get a single piece of information.
Apr 20 2014
On 4/20/14, Lars T. Kyllingstad via Digitalmars-d <digitalmars-d puremagic.com> wrote:In his article "How non-member functions improve encapsulation" [1], Scott Meyers makes a good argument for why free functions should generally be preferred over class methods in C++.TDPL actually references this IIRC.
Apr 20 2014
On Sunday, 20 April 2014 at 07:11:41 UTC, Lars T. Kyllingstad wrote:In his article "How non-member functions improve encapsulation" [1], Scott Meyers makes a good argument for why free functions should generally be preferred over class methods in C++. TL;DR: Fewer member functions means fewer functions that break when the class implementation changes, and free functions can be spread across different header files, allowing client code to only #include the ones that are needed. In D, the situation is somewhat different. Firstly, private symbols are accessible throughout the module within which the class/struct is defined, and secondly, UFC allows us to call free functions using the same syntax as member functions. In other words, *any* non-virtual function could in principle be written as a free function. The fact that "private" really means "module private" in D means that any number of functions can break when a class/struct implementation changes. So if we are to take Meyers' advice, we have to define a new module for each class/struct, and move its associated free functions to neighbouring modules. However, this would lead to a proliferation of modules that I doubt anyone would want. So, can anyone think of some good guidelines for when to make a function a member, when to write it as a free function in the same module, and when to move it to a different module? [1] http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/184401197This is a quote from Walter that sums the reasoning up perfectly: "A huge reason for them is to head off the temptation to write ‘kitchen sink’ classes that are filled with every conceivable method. The desired approach is to have the class implement the bare minimum of functionality, and add other functionality with extension methods (that do not have access to the class’ private state)." Writing classes like this allows for better encapsulation because only the required behaviour is contained within the class keeping it focused. Other methods that are useful for the class (but don't really belong in the class) can be implemented as non member function and if written in a generic way be reused throughout the program.
Apr 20 2014
On Sunday, 20 April 2014 at 11:01:27 UTC, Gary Willoughby wrote:This is a quote from Walter that sums the reasoning up perfectly: "A huge reason for them is to head off the temptation to writeWhat does "them" refer to here?‘kitchen sink’ classes that are filled with every conceivable method. The desired approach is to have the class implement the bare minimum of functionality, and add other functionality with extension methods (that do not have access to the class’ private state)."However, in D, all functions defined in the same module as a class will have access to the private state of that class, on an equal footing with its member methods. Therefore, the above statment doesn't really help in deciding which to use.Writing classes like this allows for better encapsulation because only the required behaviour is contained within the class keeping it focused. [...]I'm also pretty sure Walter has repeatedly stated that the module is the unit of encapsulation in D, not the class.
Apr 20 2014
On Sunday, 20 April 2014 at 11:12:42 UTC, Lars T. Kyllingstad wrote:On Sunday, 20 April 2014 at 11:01:27 UTC, Gary Willoughby wrote:Wouldn't this be "worked around" by packages? You place your class in a non-public subpackage package. Then you implement the non-member functions in the module proper. MyModule | +--MyPackage | | | +-MyClass | +--MyNonMemberFunctions Not sure this is "worth it" though.‘kitchen sink’ classes that are filled with every conceivable method. The desired approach is to have the class implement the bare minimum of functionality, and add other functionality with extension methods (that do not have access to the class’ private state)."However, in D, all functions defined in the same module as a class will have access to the private state of that class, on an equal footing with its member methods. Therefore, the above statment doesn't really help in deciding which to use.Writing classes like this allows for better encapsulation because only the required behaviour is contained within the class keeping it focused. [...]I'm also pretty sure Walter has repeatedly stated that the module is the unit of encapsulation in D, not the class.
Apr 20 2014
On Sunday, 20 April 2014 at 11:12:42 UTC, Lars T. Kyllingstad wrote:That may be true but doesn't detract anything from the quote and doesn't mean that classes shouldn't be well designed with good encapsulation in mind.Writing classes like this allows for better encapsulation because only the required behaviour is contained within the class keeping it focused. [...]I'm also pretty sure Walter has repeatedly stated that the module is the unit of encapsulation in D, not the class.
Apr 20 2014
On Sunday, 20 April 2014 at 11:12:42 UTC, Lars T. Kyllingstad wrote:However, in D, all functions defined in the same module as a class will have access to the private state of that class, on an equal footing with its member methods. Therefore, the above statment doesn't really help in deciding which to use.Yeah it does. If the function can be used generically across many different parts of the program then it would be much better implemented as a non-member function, even if it's defined in the same module as an associated class. Functions which are focused to only deal with data associated with a particular class then these would be better suited to be implemented as a method of that class. I'm sure there are edge cases but i'm pretty sure this is the general idea. For example: I worked on a project which included a class that internally used a method to perform some math for internal data. This method was not related to the class in any way, it just performed some calculation on data within that class. This is the sort of method that would be better served pulling out of that class and moving into a library to be reused elsewhere if needed. Thus the class becomes more focused on what its actually supposed to do while becoming more maintainable.
Apr 20 2014
On Sunday, 20 April 2014 at 13:01:53 UTC, Gary Willoughby wrote:On Sunday, 20 April 2014 at 11:12:42 UTC, Lars T. Kyllingstad wrote:I agree. If a function is generally useful outside the context of a class, it should not be defined in the class.However, in D, all functions defined in the same module as a class will have access to the private state of that class, on an equal footing with its member methods. Therefore, the above statment doesn't really help in deciding which to use.Yeah it does. If the function can be used generically across many different parts of the program then it would be much better implemented as a non-member function, even if it's defined in the same module as an associated class.Functions which are focused to only deal with data associated with a particular class then these would be better suited to be implemented as a method of that class.This is the tricky part, an it is where I have a hard time deciding which to use. For example: struct File { private int fileno; void read(ubyte[] buf) { core.sys.posix.unistd.read(fileno, buf.ptr, buf.length); } } Why, or when, is the above preferable to the following? struct File { private int fileno; } void read(File f, ubyte[] buf) core.sys.posix.unistd.read(f.fileno, buf.ptr, buf.length); } I still haven't heard any fact-based, logical arguments that advise me on which style to use, and so far it seems to be just that -- a matter of style. There are a few clear-cut cases, such as when a function should be virtual, or when it is part of a predefined interface (e.g. input range), but in the general case one seems just as "encapsulated" as the other.
Apr 21 2014
On Monday, 21 April 2014 at 08:33:21 UTC, Lars T. Kyllingstad wrote:This is the tricky part, an it is where I have a hard time deciding which to use. For example: struct File { private int fileno; void read(ubyte[] buf) { core.sys.posix.unistd.read(fileno, buf.ptr, buf.length); } } Why, or when, is the above preferable to the following? struct File { private int fileno; } void read(File f, ubyte[] buf) core.sys.posix.unistd.read(f.fileno, buf.ptr, buf.length); } I still haven't heard any fact-based, logical arguments that advise me on which style to use, and so far it seems to be just that -- a matter of style.In this case i would say go with the method and not a non-member function. There are two reasons for this. First the method uses private state which is itself a good indicator. Second 'read' is an action of 'File'. This is encapsulation in action because you are defining logical actions to be performed on the state of a class. It's a well formed unit with associated behaviours. It is confusing to think 'if this was a non-member function in the same module i can also access use the private state'. Yes that's true but just because you *can* access it, it doesn't mean you should! In fact you should have a very well defined reason for doing so. Classes should nearly always be nouns and methods nearly always be verbs. Nearly always because there are always exceptions. For example i like to name methods which return bool's starting with 'is'. e.g. isOpen, isRunning, etc. The rule is follow encapsulation[1] as much as possible when designing classes. I found accessing private state in a module is useful when *initialising* said state when constructing objects. In this case the module can act like a very helpful factory. In a recent project i have a rigid class design of an application and it's child windows. Other windows should be able to be opened and their id's generated automatically and internally. These id's are only available as read-only properties. One window in particular i needed to create with a specific non-generated id. I couldn't include this data in the constructor as i don't want anyone to control the id's but *i* needed to this one time. This case fitted well into the D module design and allowed me to create a window, override its private id and move on knowing that could not be tampered with by anything else. This design turned out to be very clean and without any baggage of a unnecessary setter methods or constructor parameter. I used to be of the ilk that thought all programming could be done using only classes and designing everything in a very strict OOP way. D has broken that way of thinking in me purely because of things like UFCS and a module's private access to members. Now i understand that you can actually achieve a cleaner design by moving towards these things instead of having everything as a class. First and foremost you must try and achieve a good OOP design, this is essential. Then use UFCS and module private access features to keep things clean and simple. Keep things logical, simple and straightforward. [1]: http://en.wikipedia.org/wiki/Encapsulation_(object-oriented_programming)
Apr 21 2014
On Monday, 21 April 2014 at 08:33:21 UTC, Lars T. Kyllingstad wrote:On Sunday, 20 April 2014 at 13:01:53 UTC, Gary Willoughby wrote:I think this view is too simple. Even if a function is generally useful you risk ending up with maintenance problems later on when you need to optimize your code. So if in doubt, make it local. Unless you write libraries the primary goal with encapsulation is not reuse, but being able to evolve, modify, refactor, optimize. So having a local wrapper on top of a generic function or just make it local until you need it somewhere else is quite acceptable IMO. (But whether it is inside the class or not is mostly syntactical?) I've one time too many done too early refactoring under the assumption that it would lead to better, resusable code. It seldom does. It often leads to a wasted design effort, less intuitive function names and more fragmented code that is harder to understand later on. Ola.Yeah it does. If the function can be used generically across many different parts of the program then it would be much better implemented as a non-member function, even if it's defined in the same module as an associated class.I agree. If a function is generally useful outside the context of a class, it should not be defined in the class.
Apr 21 2014
On Monday, 21 April 2014 at 13:03:50 UTC, Ola Fosheim Grøstad wrote:On Monday, 21 April 2014 at 08:33:21 UTC, Lars T. Kyllingstad wrote:I agree, but I think that's more a question of *when* a function is considered "generally useful". To me, that is when I have an actual use case for it beyond the one for which it was originally designed, and not just because I think it might come in handy some time in the future. "If in doubt, make it private" is always a good guideline.On Sunday, 20 April 2014 at 13:01:53 UTC, Gary Willoughby wrote:I think this view is too simple. Even if a function is generally useful you risk ending up with maintenance problems later on when you need to optimize your code. So if in doubt, make it local.Yeah it does. If the function can be used generically across many different parts of the program then it would be much better implemented as a non-member function, even if it's defined in the same module as an associated class.I agree. If a function is generally useful outside the context of a class, it should not be defined in the class.
Apr 21 2014
On 4/20/14, 12:11 AM, Lars T. Kyllingstad wrote:The fact that "private" really means "module private" in D means that any number of functions can break when a class/struct implementation changes.No, only those in that module. There's no change. -- Andrei
Apr 20 2014
On Sunday, 20 April 2014 at 20:36:58 UTC, Andrei Alexandrescu wrote:On 4/20/14, 12:11 AM, Lars T. Kyllingstad wrote:Ok, so "any number" was poorly phrased. What I meant was "a large number", because in my experience, modules tend to be quite large. Specifically, they are rarely limited to containing just a single class. They often contain multiple classes, along with most related functionality. In principle, changing the implementation of one class can break the implementation of another class! Now, you may argue that kitchen sink modules are poor programming style, but it seems to be a common style, with Phobos being a very prominent example. :) I often wish "private" meant class private in D. I know, the usual argument against this is that someone who writes a module usually has full control of that module, but again, Phobos is an example of the contrary. Each module has at least a dozen authors, even if they aren't all listed in the documentation. I also know it's never going to happen due to the amount of code breakage it would cause. But maybe we could extend the syntax a bit? E.g. "private(class)" or "class private"?The fact that "private" really means "module private" in D means that any number of functions can break when a class/struct implementation changes.No, only those in that module. There's no change. -- Andrei
Apr 21 2014
On 4/21/14, 1:49 AM, Lars T. Kyllingstad wrote:On Sunday, 20 April 2014 at 20:36:58 UTC, Andrei Alexandrescu wrote:The point here is boundedness, i.e. whether stuff that's affected is within your control or not. The module you're working on is trivially "yours". Changes visible outside the module are "unbounded" because they affect present and future client code.On 4/20/14, 12:11 AM, Lars T. Kyllingstad wrote:Ok, so "any number" was poorly phrased. What I meant was "a large number", because in my experience, modules tend to be quite large.The fact that "private" really means "module private" in D means that any number of functions can break when a class/struct implementation changes.No, only those in that module. There's no change. -- AndreiI often wish "private" meant class private in D.I think we're in good shape here. Andrei
Apr 21 2014
On Mon, 21 Apr 2014 11:02:14 -0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:On 4/21/14, 1:49 AM, Lars T. Kyllingstad wrote:Sure, but Lars' point that it completely precludes the encapsulation mechanism that Scott is advocating, is true. You would have to put the functions outside the core module to give them the same isolation as non-friend C++ global functions. Note, I'm with you that the current mechanism is the "right way." It just completely prevents that technique of encapsulation :) -SteveOn Sunday, 20 April 2014 at 20:36:58 UTC, Andrei Alexandrescu wrote:The point here is boundedness, i.e. whether stuff that's affected is within your control or not. The module you're working on is trivially "yours". Changes visible outside the module are "unbounded" because they affect present and future client code.On 4/20/14, 12:11 AM, Lars T. Kyllingstad wrote:Ok, so "any number" was poorly phrased. What I meant was "a large number", because in my experience, modules tend to be quite large.The fact that "private" really means "module private" in D means that any number of functions can break when a class/struct implementation changes.No, only those in that module. There's no change. -- AndreiI often wish "private" meant class private in D.I think we're in good shape here.
Apr 21 2014
On 4/21/14, 8:08 AM, Steven Schveighoffer wrote:Sure, but Lars' point that it completely precludes the encapsulation mechanism that Scott is advocating, is true. You would have to put the functions outside the core module to give them the same isolation as non-friend C++ global functions. Note, I'm with you that the current mechanism is the "right way." It just completely prevents that technique of encapsulation :) -SteveGot it, thanks. -- Andrei
Apr 21 2014
On 21/04/14 10:49, Lars T. Kyllingstad wrote:Ok, so "any number" was poorly phrased. What I meant was "a large number", because in my experience, modules tend to be quite large. Specifically, they are rarely limited to containing just a single class. They often contain multiple classes, along with most related functionality. In principle, changing the implementation of one class can break the implementation of another class! Now, you may argue that kitchen sink modules are poor programming style, but it seems to be a common style, with Phobos being a very prominent example. :)Phobos is a very bad example of code organizing. I'm almost exclusively organizing with one class per module and a deeper hierarchy of packages. Not saying that is the ideal solution. -- /Jacob Carlborg
Apr 21 2014
On Tuesday, 22 April 2014 at 06:39:47 UTC, Jacob Carlborg wrote:On 21/04/14 10:49, Lars T. Kyllingstad wrote:I like it much more than class-based privacy for the simple reason that I hate classes :) What is really missing here is proper `package` protection that is actually usable (can select package name it applies to). When it is implemented, you can emulate same encapsulation layout by simply replacing classes with modules and modules with packages.Ok, so "any number" was poorly phrased. What I meant was "a large number", because in my experience, modules tend to be quite large. Specifically, they are rarely limited to containing just a single class. They often contain multiple classes, along with most related functionality. In principle, changing the implementation of one class can break the implementation of another class! Now, you may argue that kitchen sink modules are poor programming style, but it seems to be a common style, with Phobos being a very prominent example. :)Phobos is a very bad example of code organizing. I'm almost exclusively organizing with one class per module and a deeper hierarchy of packages. Not saying that is the ideal solution.
Apr 24 2014
On Tuesday, 22 April 2014 at 06:39:47 UTC, Jacob Carlborg wrote:I'm almost exclusively organizing with one class per module and a deeper hierarchy of packages. Not saying that is the ideal solution.That's what i've started to do in a recent project and then use the special 'package.d' file to provide a package. Sometimes you may have a couple of related classes or enums in each file too but only if they need to be friendly with the main class. This way is much more clear and neat.
Apr 24 2014
On 21/04/2014 09:49, Lars T. Kyllingstad wrote:I often wish "private" meant class private in D. I know, the usual argument against this is that someone who writes a module usually has full control of that module, but again, Phobos is an example of the contrary. Each module has at least a dozen authors, even if they aren't all listed in the documentation.I think this is a good argument. True private aids developers when refactoring. Also synchronized classes always have true private members, so it would be better if D could express it instead of changing the meaning of 'private' for them.I also know it's never going to happen due to the amount of code breakage it would cause. But maybe we could extend the syntax a bit? E.g. "private(class)" or "class private"?Perhaps the following would work: * Add 'module' as synonym for 'private' * Add ' private' for true private access * Deprecate 'private' after some years I doubt this would be popular due to churn, but I would support it.
Apr 24 2014
On Sun, 20 Apr 2014 03:11:39 -0400, Lars T. Kyllingstad <public kyllingen.net> wrote:So, can anyone think of some good guidelines for when to make a function a member, when to write it as a free function in the same module, and when to move it to a different module?First, you rightly destroy the main reason for making a module-level function vs. a method for D -- module-level functions have access to the private data. Therefore, the motivation to make a module-level function is significantly diminished. In my opinion, I would say that you should always first try making them methods, but under certain cases, you should make them functions. Reasons off the top of my head not to make them module functions: 1. You can import individual symbols from modules. i.e.: import mymodule: MyType; If a large portion of your API is module-level functions, this means you have to either import the whole module, or the individual methods you plan to use. 2. You can get delegates to methods. You cannot get delegates to module functions, even if they are UFCS compatible. 3. There is zero chance of a conflict with another type's similarly named method. 4. It enforces the "method call" syntax. I.e. you cannot use foo(obj) call. This may be important for readability. 5. You can only use operator overloads via methods. D is different in this respect from C++. 6. The documentation will be grouped with the object itself. This becomes even more critical with the new doc layout which has one page per type. Reasons to make them module functions: 1. You have more than one object in the same file which implements the method identically via duck typing. 2. You want to change how the 'this' type is passed -- in other words, you want to pass a struct by value or by pointer instead of by ref. module-level API to be selectively enabled! 4. Of course, if you are actually implementing in a different module, Scott Meyers' reasoning applies there. -Steve
Apr 21 2014
On Monday, 21 April 2014 at 12:45:12 UTC, Steven Schveighoffer wrote:[...] Reasons off the top of my head not to make them module functions: 1. You can import individual symbols from modules. i.e.: import mymodule: MyType; If a large portion of your API is module-level functions, this means you have to either import the whole module, or the individual methods you plan to use.Based on this, combined with your points 6 and 3 further down -- the second number 3, that is :) -- we can make the following guideline: Methods which are central to the class' usage, and which are therefore likely to be used often, should be member functions, while auxiliary functions and convenience functions should be non-members. The same thing was stated earlier in this thread, in different words, and I guess it is the rule most of us use already. However, this is the first non-subjective rationale I've seen for it so far. Awesome!2. You can get delegates to methods. You cannot get delegates to module functions, even if they are UFCS compatible.This is an excellent point. I would never have thought of that.3. There is zero chance of a conflict with another type's similarly named method.How? If you have the following functions: void foo(A a); void foo(B b); and you write foo(new B); there is also zero chance of conflict -- even if B happens to be a subclass of A, since the most specialised function is always called.4. It enforces the "method call" syntax. I.e. you cannot use foo(obj) call. This may be important for readability.Some would argue that giving users the choice between typing foo(obj) and obj.foo() is a Good Thing, because it doesn't impose your preferences on them. I'm not going to do that, though. ;)5. You can only use operator overloads via methods. D is different in this respect from C++.True. Operator overloads fall in the same category as virtuals and interface functions, i.e., the ones that *cannot* be non-members.[...] Reasons to make them module functions: 1. You have more than one object in the same file which implements the method identically via duck typing. 2. You want to change how the 'this' type is passed -- in other words, you want to pass a struct by value or by pointer instead of by ref. module-level API to be selectively enabled! 4. Of course, if you are actually implementing in a different module, Scott Meyers' reasoning applies there.All very good points. This is exactly what I was looking for, thanks!
Apr 21 2014
On Mon, 21 Apr 2014 09:46:18 -0400, Lars T. Kyllingstad <public kyllingen.net> wrote:On Monday, 21 April 2014 at 12:45:12 UTC, Steven Schveighoffer wrote:Demonstration: module m1; import std.stdio; class C {} void foo(C c) { writeln("C.foo"); } void bar(C c) { writeln("C.bar"); } module m2; import m1; import std.stdio; void foo(T)(T t) { writeln("m2.foo"); } void bar(T)(T t, int x) { writeln("m2.bar"); } void main() { auto c = new C; c.foo(); // "m2.foo"; //c.bar(); // error if uncommented! } Basically, I've inadvertently overridden C.foo, without intending to. With bar, I've somehow hidden the inherent functionality of C!3. There is zero chance of a conflict with another type's similarly named method.How? If you have the following functions: void foo(A a); void foo(B b); and you write foo(new B); there is also zero chance of conflict -- even if B happens to be a subclass of A, since the most specialised function is always called.You may recall that I am a big proponent of explicit properties because I think the ways of calling functions have strong implications to the reader, regardless of the functions. This is the same thing. I look at foo(x) much differently than x.foo(). It's the same (though not quite as important) as choosing a good name for a function. -Steve4. It enforces the "method call" syntax. I.e. you cannot use foo(obj) call. This may be important for readability.Some would argue that giving users the choice between typing foo(obj) and obj.foo() is a Good Thing, because it doesn't impose your preferences on them. I'm not going to do that, though. ;)
Apr 21 2014
On Monday, 21 April 2014 at 14:10:08 UTC, Steven Schveighoffer wrote:[...] module m1; import std.stdio; class C {} void foo(C c) { writeln("C.foo"); } void bar(C c) { writeln("C.bar"); } module m2; import m1; import std.stdio; void foo(T)(T t) { writeln("m2.foo"); } void bar(T)(T t, int x) { writeln("m2.bar"); } void main() { auto c = new C; c.foo(); // "m2.foo"; //c.bar(); // error if uncommented! } Basically, I've inadvertently overridden C.foo, without intending to. With bar, I've somehow hidden the inherent functionality of C!Wow, I didn't know that. I thought the most specialised function would always be selected, regardless of which module it's defined in. What you've demonstrated feels wrong, somehow.And you may recall that I was on the same side as you in the properties debate, though less vocal about it. (In fact, I think we didn't go far enough with properties -- we should also forbid taking their address. But that's another discussion.) The point is, I lean towards the same view as you when it comes to UFCS, and only brought up the opposing view for the sake of the discussion. I tend to use UFCS only for a few select cases (array range functions, range chaining, etc.), and to otherwise use the "normal" function call syntax. If someone sees this in my code: obj.foo(); I want them to know where to look for further information about foo(), namely in the class documentation/code.You may recall that I am a big proponent of explicit properties because I think the ways of calling functions have strong implications to the reader, regardless of the functions. This is the same thing. I look at foo(x) much differently than x.foo().4. It enforces the "method call" syntax. I.e. you cannot use foo(obj) call. This may be important for readability.Some would argue that giving users the choice between typing foo(obj) and obj.foo() is a Good Thing, because it doesn't impose your preferences on them. I'm not going to do that, though. ;)
Apr 21 2014
On Monday, 21 April 2014 at 14:38:53 UTC, Lars T. Kyllingstad wrote:What you've demonstrated feels wrong, somehow.As in "it shouldn't be that way", not as in "I think you're wrong about this". :)
Apr 21 2014
On Monday, 21 April 2014 at 14:10:08 UTC, Steven Schveighoffer wrote:You may recall that I am a big proponent of explicit properties because I think the ways of calling functions have strong implications to the reader, regardless of the functions. This is the same thing. I look at foo(x) much differently than x.foo().I agree. What was the reasoning by conflating the two? I only see disadvantages: - harder to read - reduced namespace - possibility of breaking application code when adding members to libraries?
Apr 21 2014
On Mon, 21 Apr 2014 09:46:18 -0400, Lars T. Kyllingstad <public kyllingen.net> wrote:On Monday, 21 April 2014 at 12:45:12 UTC, Steven Schveighoffer wrote:I definitely restricted myself too much when I said "another type." Yes, there is a very low possibility of A and B conflicting. But as I showed in the other post, there is the possibility of confusing the compiler when calling a UFCS method. Essentially, the core issue is that a type provides the strongest tie to its method overload set. The module's overload set has looser ties, so they can be accidentally (or intentionally) overridden. It was a common con against UFCS before it was introduced. -Steve3. There is zero chance of a conflict with another type's similarly named method.How? If you have the following functions: void foo(A a); void foo(B b); and you write foo(new B); there is also zero chance of conflict -- even if B happens to be a subclass of A, since the most specialised function is always called.
Apr 21 2014
On 4/21/14, Steven Schveighoffer via Digitalmars-d <digitalmars-d puremagic.com> wrote:Reasons off the top of my head not to make them module functionsHere's another one, the bug report is about enums but it showcases an issue with module-scoped functions taking struct parameters (in short: function hijacking protection makes defining module-scoped functions problematic): https://issues.dlang.org/show_bug.cgi?id=10846
Apr 21 2014
On 04/21/14 14:45, Steven Schveighoffer via Digitalmars-d wrote:Reasons off the top of my head not to make them module functions:[...] Functions, unlike methods, do not work with rvalues. Ie struct S { long[999999] data; auto f() { return data[0]; } } auto g(ref S _this) { with (_this) return data[1]; } void main() { auto a = S().f(); auto b = S().g(); } artur
Apr 21 2014
On Monday, 21 April 2014 at 16:35:23 UTC, Artur Skawina via Digitalmars-d wrote:On 04/21/14 14:45, Steven Schveighoffer via Digitalmars-d wrote:Shouldn't this be possible if you want to make g a template function and use auto ref? Regardless, it doesn't. Probably a compiler bug: struct S { long[999999] data; auto f() { return data[0]; } } //No good //auto g(T: S)(auto ref T _this) { with (_this) return data[1]; } //Doesn't work either //auto g()(auto ref S _this) { with (_this) return data[1]; } void main() { auto a = S().f(); auto b = S().g(); }Reasons off the top of my head not to make them module functions:[...] Functions, unlike methods, do not work with rvalues. Ie struct S { long[999999] data; auto f() { return data[0]; } } auto g(ref S _this) { with (_this) return data[1]; } void main() { auto a = S().f(); auto b = S().g(); } artur
Apr 21 2014