Jump to page: 1 24  
Page
Thread overview
Required Reading: "How Non-Member Functions Improve Encapsulation"
Oct 25, 2017
Walter Bright
Oct 26, 2017
rikki cattermole
Oct 26, 2017
who cares
Oct 26, 2017
Jacob Carlborg
Oct 26, 2017
Kagamin
Oct 27, 2017
Jacob Carlborg
Oct 27, 2017
Kagamin
Oct 27, 2017
Jacob Carlborg
Oct 29, 2017
w0rp
Oct 29, 2017
Jonathan M Davis
Oct 27, 2017
Dukc
Oct 26, 2017
JN
Oct 26, 2017
Jonathan M Davis
Oct 26, 2017
Walter Bright
Oct 27, 2017
Jonathan M Davis
Oct 27, 2017
Kagamin
Oct 31, 2017
codephantom
Oct 27, 2017
Dmitry Olshansky
Oct 30, 2017
H. S. Teoh
Oct 30, 2017
Jonathan M Davis
Oct 30, 2017
H. S. Teoh
Oct 31, 2017
codephantom
Oct 31, 2017
codephantom
Oct 31, 2017
codephantom
Oct 31, 2017
Jonathan M Davis
Oct 31, 2017
Patrick Schluter
Oct 31, 2017
Dave Jones
Oct 31, 2017
H. S. Teoh
Nov 01, 2017
codephantom
Nov 01, 2017
H. S. Teoh
Oct 31, 2017
Dukc
Oct 31, 2017
H. S. Teoh
Oct 31, 2017
jmh530
October 25, 2017
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.
October 26, 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 ;)
October 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/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 :/
October 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/184401197

You mean non-member functions are preferred? I encountered this more from performance point: especially in case of small structures like Point it would be more beneficial to pass them by value than by reference, which can be achieved by extension methods, but then you need to import the respective module to have those extension methods available. It would work more palatable if extension methods from the structure's module were available without requiring import. It may rely on static declarations inside the struct like C# does it:

struct Size
{
  int width,height;
  alias add=.add; //?
}

Size add(Size s1, Size s2)
{
  return Size(s1.width+s2.width, s1.height+s2.height);
}

or just work without it. Such alias would allow to provide extension methods from other modules, though this would mean circular dependency between modules.
October 26, 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
October 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/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 :)
October 26, 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
October 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:
> > for core D devs.
> >
> > "How Non-Member Functions Improve Encapsulation" by Scott Meyers
> >
> > http://www.drdobbs.com/cpp/how-non-member-functions-improve-encapsu/1844 01197
> >
> > Note that I'm as guilty as anyone for not understanding or following these guidelines. I expect we can do much better.
>
> As a counterpoint. I guess UFCS makes it less of a problem, but it's nice when using an IDE to just type foo. , press ctrl-space and see a nice list of methods that can be used with the class. When having free functions, you don't get to enjoy that :)

At a previous job, we had our own string class which wrapped std::string just because some of the developers wanted our extra string functions on the type where they would be easy to find (even just for documentation purposes). They were of the opinion that once you separate the functions from the class, your project will ultimately end up with multiple header files of functions doing similar things, because at least some of the time, developers wouldn't know about the existing functions and would add their own own. And with sufficiently large projects, that does seem to happen all too often. I still don't agree with the idea that putting stuff on the class is better just because it makes stuff easier to find, but I can see why someone would think that.

As has been pointed out elsewhere in this thread, the encapsulation benefits don't exist in the same way in D unless you put the free functions in separate modules, and then you have to import other stuff to use them, whereas in C++, you can just put the free functions next to the type and still get the encapsulation benefits. So, the benefit of splitting the functions out within a module is fairly minimal in D. Rather, the main reason I see for splitting functions out is in order to make them more generic, and I think that the push to do that in D has a tendency to make a lot of functions free functions that might have been member functions in most other languages.

And in D, there's the issue that declaring a function to be used with UFCS instead of as a member function can be a bit of a pain when the type is templated. You have to repeat a bunch of stuff that's just part of the templated struct or class. You end up with additional templates and template constraints (much of which is duplicated) just to separate the functions from the type.

That being said, the time that I'm most likely to turn a member function into a free function when the function isn't going to be made generic is when the type is templated simply because then I can put the unittest block right next to the function without having it be part of the template (since that's almost always the wrong thing to do if you don't do some boilerplate with static ifs to make it so that they're only compiled into a single instantiation that's intended just for testing).

Overall, I think that the idea that functions should be free functions where reasonable is good advice, but it needs to be taken with a grain of salt.

- Jonathan M Davis

October 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(); }
October 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:
> > 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

« First   ‹ Prev
1 2 3 4