www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Very hacky solution to class private members

reply bauss <jj_1337 live.dk> writes:
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
parent reply bauss <jj_1337 live.dk> writes:
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
parent reply forkit <forkit gmail.com> writes:
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
next sibling parent reply bauss <jj_1337 live.dk> writes:
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. 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.
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.
Jun 09 2022
parent reply zjh <fqbqrr 163.com> writes:
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
parent zjh <fqbqrr 163.com> writes:
On Thursday, 9 June 2022 at 10:05:37 UTC, zjh wrote:

 Is `stubborn`!
So, many `Chinese` have left `D`!
Jun 09 2022
prev sibling parent reply Dom Disc <dominikus scherkl.de> writes:
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. 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.
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).
Jun 09 2022
next sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
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
parent reply Dom Disc <dominikus scherkl.de> writes:
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:
 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.
Whatever you do, it needs to be something human-readable, because the compiler can't help you there.
 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
next sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
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.
 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).
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).
Jun 09 2022
prev sibling parent forkit <forkit gmail.com> writes:
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
prev sibling next sibling parent reply bauss <jj_1337 live.dk> writes:
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:
 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.
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).
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.
Jun 09 2022
next sibling parent reply Mathias LANG <pro.mathias.lang gmail.com> writes:
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
parent reply bauss <jj_1337 live.dk> writes:
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:
 [...]

 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.
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.
Jun 09 2022
parent bauss <jj_1337 live.dk> writes:
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:
 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.
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.
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.
Jun 09 2022
prev sibling parent reply Dom Disc <dominikus scherkl.de> writes:
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
parent reply bauss <jj_1337 live.dk> writes:
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:

 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.
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.
Jun 09 2022
parent Dom Disc <dominikus scherkl.de> writes:
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:
 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++.
Because others did and C++ is the source why the design decision to make module the border for encapsulation was made.
 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
prev sibling parent reply forkit <forkit gmail.com> writes:
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
next sibling parent Prezelboss <no no.no> writes:
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:
 [...]
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.
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.
Jun 09 2022
prev sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
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