digitalmars.D - Very hacky solution to class private members
- bauss (110/110) Jun 09 2022 Below is a way to actually make class private members using a
- bauss (61/62) Jun 09 2022 Some minor modifications for the hiddenFn that allows you to pass
- forkit (5/5) Jun 09 2022 On Thursday, 9 June 2022 at 08:44:50 UTC, bauss wrote:
- bauss (6/12) Jun 09 2022 Honestly I couldn't tell you. I think it's because people have
- Dom Disc (19/25) Jun 09 2022 I still don't get why class level encapsulation should be any
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (10/15) Jun 09 2022 I personally use underscore for member functions or class fields
- Dom Disc (14/29) Jun 09 2022 Whatever you do, it needs to be something human-readable, because
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (25/37) Jun 09 2022 Yes, although for C++ private + friend is helpful. I actually
- forkit (7/10) Jun 09 2022 Authorisation and access control, are integral to OOP. Always has
- bauss (11/38) Jun 09 2022 Might as well just make everything public then, right? Why stop
- Mathias LANG (5/10) Jun 09 2022 I still don't understand why people still don't get this: The
- bauss (78/90) Jun 09 2022 But it doesn't always work putting your class in another module.
- bauss (5/96) Jun 09 2022 Here's a relevant dead DIP for this:
- Dom Disc (21/28) Jun 09 2022 No. I have written reducing the search-space to one file *DOES*
- forkit (15/18) Jun 09 2022 Well, it's not necessarily better - either way.
- Prezelboss (4/21) Jun 09 2022 If you don't want members to be visible to the whole module, why
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (14/16) Jun 09 2022 In an ideal world, yes. In the practice you have to think about
Below is a way to actually make class private members using a modified version of the CTFE RNG demonstrated at d-idioms: https://p0nce.github.io/d-idioms/#Compile-time-RNG and some traits checking for calling function. It's very hacky and I don't think it should be used seriously. I was just wondering if it could be done like this and decided to do it for fun. It only supports properties and functions. First we need some boilerplate code: ```d ulong timestampToUlong(string stamp) { ulong result; foreach_reverse(c; stamp) { result += c; result *= 10; } return result; } enum counter(T,size_t x = [__traits(allMembers, mixin(T))].length)=x; char[] member(ulong x) { char[] buf = "void[0] _X0000000000000000;".dup; enum mapping = "0123456789abcdef"; foreach_reverse(i; buf.length-17 .. buf.length-1) { buf[i] = mapping[x & 0xf]; x >>= 4; } return buf; } mixin template next(T) { mixin(member(counter!(T))); } template xorShift(T,size_t x = counter!(T)) { static if(x == 0) { enum xorShift = timestampToUlong(__TIMESTAMP__); } else { enum y = xorShift!(T,x-1); enum xorShift = 0x2545_f491_4f6c_dd1dUL * (((y ^ (y >> 12)) ^ ((y ^ (y >> 12)) << 25)) ^ (((y ^ (y >> 12)) ^ ((y ^ (y >> 12)) << 25)) >> 27)); } } mixin template hiddenProp(T, string name, Within, string ts = to!string(xorShift!(Within))) { mixin(T.stringof ~ " _hidden_" ~ ts ~ "_" ~ name ~ ";"); mixin("private property " ~ T.stringof ~ " " ~ name ~ "(string caller=__FUNCTION__)() if (caller.startsWith(`" ~ fullyQualifiedName!(Within) ~ "`)) {return _hidden_" ~ ts ~ "_" ~ name ~ ";}"); mixin("private property void " ~ name ~ "(string caller=__FUNCTION__)(" ~ T.stringof ~ " value) if (caller.startsWith(`" ~ fullyQualifiedName!(Within) ~ "`)) { _hidden_" ~ ts ~ "_" ~ name ~ " = value;}"); mixin next!(Within); } mixin template hiddenFn(string name, string fnBody, string[] params = [], T = void, Within, string ts = to!string(xorShift!(Within))) { mixin(T.stringof ~ " " ~ name ~ "(string caller=__FUNCTION__)(" ~ params.join(",") ~ ") if (caller.startsWith(`" ~ fullyQualifiedName!(Within) ~ "`)) { " ~ fnBody ~ " }"); mixin next!(Within); } ``` And then we have the actual code for the class etc. ```d public class Foo { mixin hiddenProp!(int, "x", typeof(this)); mixin hiddenProp!(int, "y", typeof(this)); mixin hiddenFn!("calc", q{ return x * y; }, ["int x", "int y"], int, typeof(this)); void test() { x = calc(20, 5); // ok writeln(x); // ok } } void main() { auto foo = new Foo; foo.test(); // ok - prints 100 //foo.x = 300; // not ok (compile time error.) //writeln(foo.x); // not ok (compile time error.) //int z = foo.calc(); // not ok (compile time error.) //writeln(z); } ``` It's unbelievably stupid, but it works. In theory you can cheat it by changing the caller to whatever you desire or you can attempt to guess the backing variables for the properties (although that's almost impossible.) - neither is worth the trouble. You can't however accidentally call this from outside the class. Anyway just wanted to have some fun, but please don't use this garbage in all seriousness.
Jun 09 2022
On Thursday, 9 June 2022 at 07:24:24 UTC, bauss wrote:...Some minor modifications for the hiddenFn that allows you to pass types to a template instead of an array of the parameters. Also allows one to use a "lambda" for the function body instead of a string. This lambda has to take the same amount of parameters that the function takes. So now it can be done like this: ```d mixin hiddenFn!("calc", (x,y){ return x * y; }, Params!(int,int), int, typeof(this)); ``` Instead of how to do it before: ```d mixin hiddenFn!("calc", q{ return x * y; }, ["int x", "int y"], int, typeof(this)); ``` Boilerplate changes: ```d // Can't use std.algorithm.map in the mixin template, so we need some terrible function like this ... static auto stringMap(alias p)(string[] r) { string[] result = []; foreach (n; r) result ~= p(n); return result; } template Params(T...) { enum Params = ({ string[] result; static foreach(i; 0 .. T.length) { result ~= T[i].stringof ~ " _" ~ to!string(i); } return result; })(); } mixin template hiddenFn(string name, alias fnBody, string[] params = [], T = void, Within, string ts = to!string(xorShift!(Within))) { static if (is(T == void)) { mixin(T.stringof ~ " " ~ name ~ "(string caller=__FUNCTION__)(" ~ params.join(",") ~ ") if (caller.startsWith(`" ~ fullyQualifiedName!(Within) ~ "`)) { fnBody(" ~ stringMap!(p => p.split(" ")[$-1])(params).join(",") ~ "); }"); } else { mixin(T.stringof ~ " " ~ name ~ "(string caller=__FUNCTION__)(" ~ params.join(",") ~ ") if (caller.startsWith(`" ~ fullyQualifiedName!(Within) ~ "`)) { return fnBody(" ~ stringMap!(p => p.split(" ")[$-1])(params).join(",") ~"); }"); } mixin next!(Within); } ```
Jun 09 2022
On Thursday, 9 June 2022 at 08:44:50 UTC, bauss wrote:honestly. how is it, that a programming language can enable you to do all this amazing stuff, like that, and yet not provide an access control option (let alone an enforceable one) at the class level.
Jun 09 2022
On Thursday, 9 June 2022 at 09:22:52 UTC, forkit wrote:On Thursday, 9 June 2022 at 08:44:50 UTC, bauss wrote:Honestly I couldn't tell you. I think it's because people have such strong political views around here that the majority wouldn't want change, even if that change has no impact on their workflow or even their code. The community for D is much like its GC, very conservative.honestly. how is it, that a programming language can enable you to do all this amazing stuff, like that, and yet not provide an access control option (let alone an enforceable one) at the class level.
Jun 09 2022
On Thursday, 9 June 2022 at 09:49:14 UTC, bauss wrote:The community for D is much like its GC, very conservative.Is `stubborn`!
Jun 09 2022
On Thursday, 9 June 2022 at 10:05:37 UTC, zjh wrote:Is `stubborn`!So, many `Chinese` have left `D`!
Jun 09 2022
On Thursday, 9 June 2022 at 09:22:52 UTC, forkit wrote:On Thursday, 9 June 2022 at 08:44:50 UTC, bauss wrote:I still don't get why class level encapsulation should be any better than module level encapsulation. You need it to prevent some function from accidentally use things it shouldn't use, yes? Because if you _want_ to circumvent the privacy, there are always ways to do that. But what about all the member-functions of a class, that also should not have access to some variables? (Because most of the time, private members should only be accessed by a few members that are designed to work with them). There neither private or hidden on whatever level can help you. So, whatever you do, the only way to review a class is to simply check that all usages of a private variable are justified at the place where it is used. And that becomes easier if you have to check only one file, but if it is only used in a part of a file doesn't reduce the effort the slightest bit. Zero. No effect. So I just can't understand why you insist on class level encapsulation. It brings no benefit at all (at least regarding the effort for reviews).honestly. how is it, that a programming language can enable you to do all this amazing stuff, like that, and yet not provide an access control option (let alone an enforceable one) at the class level.
Jun 09 2022
On Thursday, 9 June 2022 at 10:03:02 UTC, Dom Disc wrote:But what about all the member-functions of a class, that also should not have access to some variables? (Because most of the time, private members should only be accessed by a few members that are designed to work with them). There neither private or hidden on whatever level can help you.I personally use underscore for member functions or class fields that has to be handled with care. But if you look at the D standard library you see that some modules are convoluted and in such settings it would be better with another protection level. In general if somebody wants to improve the language then they can find many of the problem spots in the D standard library and use that for making judgments. If someone ever started on D3, that is the place to start IMO.
Jun 09 2022
On Thursday, 9 June 2022 at 10:26:31 UTC, Ola Fosheim Grøstad wrote:On Thursday, 9 June 2022 at 10:03:02 UTC, Dom Disc wrote:Whatever you do, it needs to be something human-readable, because the compiler can't help you there.But what about all the member-functions of a class, that also should not have access to some variables? (Because most of the time, private members should only be accessed by a few members that are designed to work with them). There neither private or hidden on whatever level can help you.I personally use underscore for member functions or class fields that has to be handled with care.But if you look at the D standard library you see that some modules are convoluted and in such settings it would be better with another protection level.No. If something is convoluted, you need to sort it out and split into two modules. Another protection level won't help. Because, as I said, a new level won't reduce your effort to understand whats happening, you still need to inspect everything within the file. But splitting would help a lot with that.In general if somebody wants to improve the language then they can find many of the problem spots in the D standard library and use that for making judgments.Jup. But new privacy-levels are no solution to them.If someone ever started on D3, that is the place to start IMO.Sorting things out should be possible without need for an new fork. It should be done withing the actual phobos as it wouldn't change the interface (at least not much).
Jun 09 2022
On Thursday, 9 June 2022 at 10:39:17 UTC, Dom Disc wrote:Whatever you do, it needs to be something human-readable, because the compiler can't help you there.Yes, although for C++ private + friend is helpful. I actually would want friend in C++ to be more limiting, so that you could say "this function is allowed to call this member, but nothing else". Mostly relevant for concurrency issues, could be useful in that context as finding such bugs is extremely time consuming. Tighter encapsulation is valuable when you change/restructure your codebase, but it equally important to have clean syntax that is easy to read!If something is convoluted, you need to sort it out and split into two modules. Another protection level won't help.I would not want to write such convoluted modules, but maybe others do.Because, as I said, a new level won't reduce your effort to understand whats happening, you still need to inspect everything within the file. But splitting would help a lot with that.You don't have to inspect everything in the file. For instance, in C++ I often let an inner class (class within a class) be protected against methods in the outer class. Also, in Simula a class and module was the same construct. A class was used to represent what D uses a module for.So this is my perspective: Phobos is one of those «larger» codebases where people have made an effort to use D constructs to write «good» code to the best of their ability. We cannot expect better for the average D programmer. So that is a realistic picture of what D code over time will look like. As such, one can study it to figure out where the language has room for improvement. You could also improve on Phobos, but that is less important (if it works).If someone ever started on D3, that is the place to start IMO.Sorting things out should be possible without need for an new fork. It should be done withing the actual phobos as it wouldn't change the interface (at least not much).
Jun 09 2022
On Thursday, 9 June 2022 at 10:39:17 UTC, Dom Disc wrote:.... Jup. But new privacy-levels are no solution to them. ..Authorisation and access control, are integral to OOP. Always has been, always will be. Any 'new' privacy-level, would only be 'new' to D ;-) C++ had the 'friend' issue. D solved that. Horray! Let's just make everyone 'forever' friends. Now D has the 'unfriend' issue.
Jun 09 2022
On Thursday, 9 June 2022 at 10:03:02 UTC, Dom Disc wrote:On Thursday, 9 June 2022 at 09:22:52 UTC, forkit wrote:Might as well just make everything public then, right? Why stop there. Removing private, protected, package etc. is the way to go then? I mean, you can review them all yourself and make sure they're not used incorrectly. The flawed mentality in your answer is that you forget that the point of it all is to cut down the amount of code you have to review. There's no reason to do the analysis yourself if the compiler can do it for you in 99% of the cases.On Thursday, 9 June 2022 at 08:44:50 UTC, bauss wrote:I still don't get why class level encapsulation should be any better than module level encapsulation. You need it to prevent some function from accidentally use things it shouldn't use, yes? Because if you _want_ to circumvent the privacy, there are always ways to do that. But what about all the member-functions of a class, that also should not have access to some variables? (Because most of the time, private members should only be accessed by a few members that are designed to work with them). There neither private or hidden on whatever level can help you. So, whatever you do, the only way to review a class is to simply check that all usages of a private variable are justified at the place where it is used. And that becomes easier if you have to check only one file, but if it is only used in a part of a file doesn't reduce the effort the slightest bit. Zero. No effect. So I just can't understand why you insist on class level encapsulation. It brings no benefit at all (at least regarding the effort for reviews).honestly. how is it, that a programming language can enable you to do all this amazing stuff, like that, and yet not provide an access control option (let alone an enforceable one) at the class level.
Jun 09 2022
On Thursday, 9 June 2022 at 10:40:01 UTC, bauss wrote:[...] Might as well just make everything public then, right? Why stop there. Removing private, protected, package etc. is the way to go then? [...]I still don't understand why people still don't get this: The module is the unit of encapsulation in D. If you want your class to be truly inaccessible, put it in its own module. Don't follow Phobos' example: Keep your modules small.
Jun 09 2022
On Thursday, 9 June 2022 at 10:59:53 UTC, Mathias LANG wrote:On Thursday, 9 June 2022 at 10:40:01 UTC, bauss wrote:But it doesn't always work putting your class in another module. Because sometimes it's not __all__ members you want to restrict access to, but only some. And also there are some scenarios where it doesn't even work properly when put into your own module. Here is an example where it does not work: a.d ```d module a; class Foo { private: int _c; } import b; void handle(Bar child) { // This fails even tho we're in module a, // so _c should be available to us, because // _c is private to the module (but not really.) child._c += child.c; } ``` b.d ```d module b; import a; class Bar : Foo { public: int c; this(int c) { this.c = c; } } ``` main.d ```d module main; import a; import b; void main() { auto bar = new Bar(30); handle(bar); } ``` The above fails to compile. However this works: a.d ```d module a; class Foo { private: int _c; } class Bar : Foo { public: int c; this(int c) { this.c = c; } } void handle(Bar child) { // It's okay now since Bar is in the a module. child._c += child.c; } ``` (main.d is the same) So you can't always split classes into new modules that easily. It really should work in both cases since we're both accessing _c from within the module, but there's clearly a difference.[...] Might as well just make everything public then, right? Why stop there. Removing private, protected, package etc. is the way to go then? [...]I still don't understand why people still don't get this: The module is the unit of encapsulation in D. If you want your class to be truly inaccessible, put it in its own module. Don't follow Phobos' example: Keep your modules small.
Jun 09 2022
On Thursday, 9 June 2022 at 11:28:57 UTC, bauss wrote:On Thursday, 9 June 2022 at 10:59:53 UTC, Mathias LANG wrote:Here's a relevant dead DIP for this: https://wiki.dlang.org/DIP22 As noted, D doesn't even know what private should mean as it certainly doesn't mean "private to the module" only.On Thursday, 9 June 2022 at 10:40:01 UTC, bauss wrote:But it doesn't always work putting your class in another module. Because sometimes it's not __all__ members you want to restrict access to, but only some. And also there are some scenarios where it doesn't even work properly when put into your own module. Here is an example where it does not work: a.d ```d module a; class Foo { private: int _c; } import b; void handle(Bar child) { // This fails even tho we're in module a, // so _c should be available to us, because // _c is private to the module (but not really.) child._c += child.c; } ``` b.d ```d module b; import a; class Bar : Foo { public: int c; this(int c) { this.c = c; } } ``` main.d ```d module main; import a; import b; void main() { auto bar = new Bar(30); handle(bar); } ``` The above fails to compile. However this works: a.d ```d module a; class Foo { private: int _c; } class Bar : Foo { public: int c; this(int c) { this.c = c; } } void handle(Bar child) { // It's okay now since Bar is in the a module. child._c += child.c; } ``` (main.d is the same) So you can't always split classes into new modules that easily. It really should work in both cases since we're both accessing _c from within the module, but there's clearly a difference.[...] Might as well just make everything public then, right? Why stop there. Removing private, protected, package etc. is the way to go then? [...]I still don't understand why people still don't get this: The module is the unit of encapsulation in D. If you want your class to be truly inaccessible, put it in its own module. Don't follow Phobos' example: Keep your modules small.
Jun 09 2022
On Thursday, 9 June 2022 at 10:40:01 UTC, bauss wrote:Might as well just make everything public then, right?No. I have written reducing the search-space to one file *DOES* reduce the effort. But reducing it to a part of a file (which class level privacy would do) does not, because still all member-functions that shouldn't have access to the privates still have to be reviewed. You still have to search the whole file to find all usages of private variables, so you gain nothing.Why stop there. Removing private, protected, package etc. is the way to go then?Nonsense. Nobody wants that.the point of it all is to cut down the amount of code you have to review.Yup. And the reduction gained by class level in addition to module level (which is not available in C++) is neglictable.There's no reason to do the analysis yourself if the compiler can do it for you in 99% of the cases.But it can't. You have to check the member functions manually anyway. On module level you have to additionally check the handful of function that are also in the same file. But in C++ instead you have to check the additionally friends, which may be scattered all over the whole project. So in comparison with C++ module level privacy is just better (if you don't put everything in one file). Within D alone, we would gain nearly nothing by an additional level.
Jun 09 2022
On Thursday, 9 June 2022 at 11:04:31 UTC, Dom Disc wrote:On Thursday, 9 June 2022 at 10:40:01 UTC, bauss wrote:I don't know why you keep mentioning C++ and how D improves over C++. Nobody mentioned C++, I certainly didn't and I really don't care if D improves over C++ or not or what their differences are. I'm not a C++ programmer and I don't come from a C++ background.Might as well just make everything public then, right?No. I have written reducing the search-space to one file *DOES* reduce the effort. But reducing it to a part of a file (which class level privacy would do) does not, because still all member-functions that shouldn't have access to the privates still have to be reviewed. You still have to search the whole file to find all usages of private variables, so you gain nothing.Why stop there. Removing private, protected, package etc. is the way to go then?Nonsense. Nobody wants that.the point of it all is to cut down the amount of code you have to review.Yup. And the reduction gained by class level in addition to module level (which is not available in C++) is neglictable.There's no reason to do the analysis yourself if the compiler can do it for you in 99% of the cases.But it can't. You have to check the member functions manually anyway. On module level you have to additionally check the handful of function that are also in the same file. But in C++ instead you have to check the additionally friends, which may be scattered all over the whole project. So in comparison with C++ module level privacy is just better (if you don't put everything in one file). Within D alone, we would gain nearly nothing by an additional level.
Jun 09 2022
On Thursday, 9 June 2022 at 11:33:08 UTC, bauss wrote:On Thursday, 9 June 2022 at 11:04:31 UTC, Dom Disc wrote:Because others did and C++ is the source why the design decision to make module the border for encapsulation was made.Within D alone, we would gain nearly nothing by an additional level.I don't know why you keep mentioning C++ and how D improves over C++.I'm not a C++ programmer and I don't come from a C++ background.May be. But D does come from a C and C++ background. Also the last sentence of what you cited was explicitly NOT about C++ but about D only.
Jun 09 2022
On Thursday, 9 June 2022 at 10:03:02 UTC, Dom Disc wrote:... I still don't get why class level encapsulation should be any better than module level encapsulation.Well, it's not necessarily better - either way. If I can encapsulate one concept well, within a class, why must I be forced to expand my 'encapsulation barrier' to the module. What have I achieved in doing that? I mean even a function in a module has a better encapsulation barrier than a class! Well, even an int does - you can't just put "wtf!" into an int. An int has a set of invariants that must be maintained, and are, by the compiler. Smaller abstraction are easier to reason about too. An encapsulation barrier at the module level, is also very useful. But it should be your design decision, not the language forcing it onto you. With the proper access levels (i.e scope level privacy), the problem just goes away...no need to do silly workarounds.
Jun 09 2022
On Thursday, 9 June 2022 at 12:42:22 UTC, forkit wrote:On Thursday, 9 June 2022 at 10:03:02 UTC, Dom Disc wrote:If you don't want members to be visible to the whole module, why do you insist on putting the class definition into the module scope in the first place? just put it inside a function.[...]Well, it's not necessarily better - either way. If I can encapsulate one concept well, within a class, why must I be forced to expand my 'encapsulation barrier' to the module. What have I achieved in doing that? I mean even a function in a module has a better encapsulation barrier than a class! Well, even an int does - you can't just put "wtf!" into an int. An int has a set of invariants that must be maintained, and are, by the compiler. Smaller abstraction are easier to reason about too. An encapsulation barrier at the module level, is also very useful. But it should be your design decision, not the language forcing it onto you. With the proper access levels (i.e scope level privacy), the problem just goes away...no need to do silly workarounds.
Jun 09 2022
On Thursday, 9 June 2022 at 12:42:22 UTC, forkit wrote:But it should be your design decision, not the language forcing it onto you.In an ideal world, yes. In the practice you have to think about the limitations of the language when you do modelling. All the C dialects come with their own set of limitations… If you compare D to other C-dialects the orignal D quality was to be more *simple* than languages such as C++ and Objective-C++. As such, the module-level thinking is a simplification. Other aspects of the language has broken with the "make it simple" vision, which is unfortunate, but difficult to undo. To a large extent this feature-creep is due to giving in to demands and whims and not resisting ideas that are "not simpler". I guess you could argue that resisting class-privacy is the wrong feature to resist, but objectively speaking: D should absolutely resist more feature bloat and only give in where it truly matters.
Jun 09 2022