Required Reading: "How Non-Member Functions Improve Encapsulation"

Jonathan M Davis newsgroup.d at jmdavisprog.com
Fri Oct 27 00:37:58 UTC 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:
> > As has been pointed out elsewhere in this thread, the encapsulation
> > benefits don't exist in the same way in D unless you put the free
> > functions in separate modules, and then you have to import other stuff
> > to use them, whereas in C++, you can just put the free functions next
> > to the type and still get the encapsulation benefits. So, the benefit
> > of splitting the functions out within a module is fairly minimal in D.
> > Rather, the main reason I see for splitting functions out is in order
> > to make them more generic, and I think that the push to do that in D
> > has a tendency to make a lot of functions free functions that might
> > have been member functions in most other languages.
>
> The point is that functions that do not need access to private fields
> should NOT be part of the class. Most of the discussion here is based on
> the idea that those functions should still be semantically part of the
> class, even if not physically. That idea should be revisited. Why should
> they be semantically part of the class?
>
>
> You can also do things like:
>
> --- s.d -------
> struct S { int x; ref int X() { return x; } }
>
> --- splus.d ----
> public import s;
> int increment(S s) { s.X() += 1; } // note no access to S.x
>
> --- user.d ----
> import splus;
> void foo(ref S s) { s.increment(); }

I agree that it's a good rule of thumb to aim for member functions to be the
set of functions that require access to private members, but I've never felt
that it's useful to be pedantic about it (particularly in D, where you don't
even get encapsulation if they're in the same module). When I write a struct
or class, it's generally designed with a set of operations that conceptually
go with it, and I don't see whether they need to access to private members
as being all that relevant to that. Functions which aren't really
conceptually part of the class or struct certainly should be separate, but
for me at least, it's about the API and what it's conceptually trying to do,
and what the type is trying to be and represent - what its abstraction is.
And in some cases, separating a function from a type just because it doesn't
happen to use any private members then feels like an artificial separation.

On some level, I think that it comes down to a question of what operations
are part of the abstraction and what operations are just using the
abstraction, if it's part of the abstraction, IMHO, it just makes more sense
for it to be a member function regardless of whether it accesses private
members.

Obviously, that's subjective, and there's then disagreement at least some of
the time on what should and shouldn't be a member function, but I don't
think that it's necessarily the case that having everything that doesn't
need access to private members as free functions leads to the abstraction
that a struct or class presents being very clean.

And once you get into classes and inheritance, breaking things up based on
what needs access to private members definitely falls apart in a number of
circumstances, because then you're very clearly abstracting behaviors (which
can then be overidden) as opposed to simply encapsulating data and operating
on it like some structs do. I just tend to think of types in general that
way, not just those involving interfaces and inheritance.

If you're coming at the type from the standpoint that everything that is
conceptually part of the type is actually part of the type, and everything
else is separate, then on some level, that will follow the idea that
functions that access private members are member functions and those that
don't aren't, but at least some of the time, it won't. And in that case, I'm
generally going to make it a member function.

I think that the problem is when folks just add functions to a type when
they don't need to be member functions to do what they do and really aren't
conceptually part of the type. They're just put on there because it's easy
or because that's what folks are used to having to do in languages like Java
or because it works better with auto-completion (which honestly, I think is
one area where IDEs make things worse; having auto-completion is fine, but
writing code in a certain way because of auto-completion is an anti-pattern
IMHO). Every time that a programmer is adding a function, they really should
be considering whether it really should be a member function or whether it
makes more sense for it to be a free function.

So, I think that the advice that member functions should generally be the
set of functions that need access to private members is good think to about
and something that programmers should keep in mind, but I also don't think
that that's really the best dividing line in general as to what should and
shouldn't be a member function. And thanks to how access levels work in D,
it's even less useful as a goal than it would be in C++, because structs and
classes simply aren't encapsulated in the same way, even if programmers
don't normally write code which breaks the encapsulation of structs and
classes unless they need to for the same reasons that you'd write a friend
function in C++.

- Jonathan M Davis



More information about the Digitalmars-d mailing list