www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Do non-member functions improve encapsulation in D?

reply "Lars T. Kyllingstad" <public kyllingen.net> writes:
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
next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
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/184401197

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. 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
prev sibling next sibling parent "Rikki Cattermole" <alphaglosined gmail.com> writes:
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/184401197

My 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
prev sibling next sibling parent "Lars T. Kyllingstad" <public kyllingen.net> writes:
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
prev sibling next sibling parent Andrej Mitrovic via Digitalmars-d <digitalmars-d puremagic.com> writes:
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
prev sibling next sibling parent "Gary Willoughby" <dev nomad.so> writes:
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/184401197

This 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
prev sibling next sibling parent "Lars T. Kyllingstad" <public kyllingen.net> writes:
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 write

What 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
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
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:
 ‘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.

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.
Apr 20 2014
prev sibling next sibling parent "Gary Willoughby" <dev nomad.so> writes:
On Sunday, 20 April 2014 at 11:12:42 UTC, Lars T. Kyllingstad 
wrote:
 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.

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.
Apr 20 2014
prev sibling next sibling parent "Gary Willoughby" <dev nomad.so> writes:
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
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 4/21/14, 1:49 AM, Lars T. Kyllingstad wrote:
 On Sunday, 20 April 2014 at 20:36:58 UTC, Andrei Alexandrescu wrote:
 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

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 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.
 I often wish "private" meant class private in D.

I think we're in good shape here. Andrei
Apr 21 2014
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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 :)

 -Steve

Got it, thanks. -- Andrei
Apr 21 2014
prev sibling next sibling parent Jacob Carlborg <doob me.com> writes:
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
prev sibling parent Nick Treleaven <ntrel-public yahoo.co.uk> writes:
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
prev sibling next sibling parent "Lars T. Kyllingstad" <public kyllingen.net> writes:
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:
 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.

I agree. If a function is generally useful outside the context of a class, it should not be defined in the 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
prev sibling next sibling parent "Lars T. Kyllingstad" <public kyllingen.net> writes:
On Sunday, 20 April 2014 at 20:36:58 UTC, Andrei Alexandrescu 
wrote:
 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

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"?
Apr 21 2014
prev sibling next sibling parent "Gary Willoughby" <dev nomad.so> writes:
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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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. 3. The complement to #1 in the 'against' list -- you want your 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
prev sibling next sibling parent "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= writes:
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:
 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.

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.
Apr 21 2014
prev sibling next sibling parent "Lars T. Kyllingstad" <public kyllingen.net> writes:
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.

 3. The complement to #1 in the 'against' list -- you want your 
 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
prev sibling next sibling parent "Lars T. Kyllingstad" <public kyllingen.net> writes:
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:
 On Sunday, 20 April 2014 at 13:01:53 UTC, Gary Willoughby 
 wrote:
 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.

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.

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.
Apr 21 2014
prev sibling next sibling parent Andrej Mitrovic via Digitalmars-d <digitalmars-d puremagic.com> writes:
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 functions

Here'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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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:
 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.

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!
 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. ;)

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. -Steve
Apr 21 2014
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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:

 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.

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. -Steve
Apr 21 2014
prev sibling next sibling parent "Lars T. Kyllingstad" <public kyllingen.net> writes:
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.
 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. ;)

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().

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.
Apr 21 2014
prev sibling next sibling parent "Lars T. Kyllingstad" <public kyllingen.net> writes:
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
prev sibling next sibling parent "Ola Fosheim =?UTF-8?B?R3LDuHN0YWQi?= writes:
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
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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:
 On Sunday, 20 April 2014 at 20:36:58 UTC, Andrei Alexandrescu wrote:
 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

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 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.
 I often wish "private" meant class private in D.

I think we're in good shape here.

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 :) -Steve
Apr 21 2014
prev sibling next sibling parent Artur Skawina via Digitalmars-d <digitalmars-d puremagic.com> writes:
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
prev sibling next sibling parent "Meta" <jared771 gmail.com> writes:
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:
 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

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(); }
Apr 21 2014
prev sibling next sibling parent "Dicebot" <public dicebot.lv> writes:
On Tuesday, 22 April 2014 at 06:39:47 UTC, Jacob Carlborg wrote:
 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.

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.
Apr 24 2014
prev sibling parent "Gary Willoughby" <dev nomad.so> writes:
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