Thread overview | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
October 05, 2013 My design need friends | ||||
---|---|---|---|---|
| ||||
Hello. I hope one of you has a good idea to solve my design problem. I have 3 files in two different sub packages. Package Bar has the interface Drawable: ---- module Bar.Drawable; interface Drawable { protected: void _render(); package: final void render() { this._render(); } } ---- and, for example, the class Graphic: ---- module Bar.Graphic; import std.stdio; import Bar.Drawable; class Graphic : Drawable { protected: override void _render() const { writeln("Graphics.render"); } } ---- Package Foo has the class Window: ---- module Foo.Window; import std.stdio; import Bar.Drawable; class Window { public: void draw(Drawable d) { writeln("Window.draw"); d.render(); /// Error: interface Bar.Drawable.Drawable member render is not accessible } } ---- As you can see, I try to access the render method from drawable. But since Drawable is in another sub package, I have no access. That is a situation where I liked to have 'friends' like in C++. I have two solutions: 1. Move Window from package Foo to the package Bar. But that's a ugly solution and I don't want to have any Window components in my Bar package. 2. Make 'render' public instead package. That is my current workaround. But I don't like it much since each other could also access 'render', not only Window. My question is: is there any other possible solutions? Any D magic? Thanks in advance. |
October 05, 2013 Re: My design need friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to Namespace | It isn't really a fix, but in these situations what I've been doing in my code is just writing: // don't use this /* private */ final public void foo() {} final makes sure it doesn't get overridden wrongly, and then the comments make my intention a little more clear. |
October 05, 2013 Re: My design need friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to Adam D. Ruppe | On Saturday, 5 October 2013 at 21:46:19 UTC, Adam D. Ruppe wrote:
> It isn't really a fix, but in these situations what I've been doing in my code is just writing:
>
> // don't use this
> /* private */ final public void foo() {}
>
> final makes sure it doesn't get overridden wrongly, and then the comments make my intention a little more clear.
Yeah, or I let the render method uncommented, so that nobody can see it in the resulting DDoc. :D
But this is no real solution. Maybe we should convince Walter for something like 'friend'?
I know that we have something similar with classes in the same module, but that is ugly and unuseable in my situation.
|
October 05, 2013 Re: My design need friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to Namespace | On Saturday, 5 October 2013 at 21:57:58 UTC, Namespace wrote:
> On Saturday, 5 October 2013 at 21:46:19 UTC, Adam D. Ruppe wrote:
>> It isn't really a fix, but in these situations what I've been doing in my code is just writing:
>>
>> // don't use this
>> /* private */ final public void foo() {}
>>
>> final makes sure it doesn't get overridden wrongly, and then the comments make my intention a little more clear.
>
> Yeah, or I let the render method uncommented, so that nobody can see it in the resulting DDoc. :D
> But this is no real solution. Maybe we should convince Walter for something like 'friend'?
> I know that we have something similar with classes in the same module, but that is ugly and unuseable in my situation.
Something that would expand the package modifier, so that it belongs to the main package and not to the subpackage, would also fulfill the purpose. Something like 'internal'.
|
October 05, 2013 Re: My design need friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to Adam D. Ruppe | On 10/5/13, Adam D. Ruppe <destructionator@gmail.com> wrote:
> It isn't really a fix, but in these situations what I've been doing in my code is just writing:
>
> // don't use this
> /* private */ final public void foo() {}
Me too, as have other library writers (like DFL).
|
October 05, 2013 Re: My design need friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to Andrej Mitrovic | I have now a solution. A bit dirty but it's the D magic I expected. New file: ---- module Core.Friend; struct Friend { public: immutable string friend; } ---- Drawable looks now like this: ---- module Bar.Drawable; import Core.Friend; @Friend("Window") interface Drawable { protected: void _render(); package: final void render() { this._render(); } } ---- And window looks like this: ---- class Window { public: void draw(Drawable d) { writeln("Window.draw"); //d.render(); /// Error: interface Bar.Drawable.Drawable member render is not accessible Accessor.friendCall!(Window, "render")(d); } } ---- As you can see, we have now an 'Accessor': ---- module Bar.Accessor; import std.stdio; import std.string : format; import Core.Friend; abstract final class Accessor { public: static void friendCall(Request, string method, T, Args...)(ref T obj, Args args) { auto friend = __traits(getAttributes, T); static if (friend.length != 0 && is(typeof(friend[0]) == Friend)) { if (friend[0].friend == __traits(identifier, Request)) { mixin("obj." ~ method ~ "(args);"); } else { throw new Exception(format("%s is not a friend of %s.", __traits(identifier, Request), __traits(identifier, T))); } } else { throw new Exception(format("%s has no friends.", __traits(identifier, T))); } } } ---- Any further suggestions or improvements? ---- |
October 05, 2013 Re: My design need friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to Namespace | Even better as mixin template: Accessor: ---- module Core.Accessor; import std.stdio; import std.string : format; import Core.Friend; mixin template Accessor(T) { public: void friendCall(string method, Request, Args...)(ref const Request caller, Args args) { auto friends = __traits(getAttributes, T); static if (friends.length != 0 && is(typeof(friends[0]) == Friend)) { foreach (ref const Friend friend; friends) { if (friend.friend == __traits(identifier, Request)) { mixin("return this." ~ method ~ "(args);"); } } throw new Exception(format("%s is not a friend of %s.", __traits(identifier, Request), __traits(identifier, T))); } else { throw new Exception(format("%s has no friends.", __traits(identifier, T))); } } } ---- Drawable: ---- @Friend("Window") interface Drawable { protected: void _render(); package: final void render() { this._render(); } public: mixin Accessor!Drawable; } ---- And Window: ---- class Window { public: void draw(Drawable d) { writeln("Window.draw"); //d.render(); /// Error: interface Bar.Drawable.Drawable member render is not accessible d.friendCall!("render")(this); } } ---- Perhaps something should be added to std.typecons. |
October 06, 2013 Re: My design need friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to Namespace | On 10/6/13, Namespace <rswhite4@googlemail.com> wrote:
> Any further suggestions or improvements?
That's nice.
You could also hack away with templates, extracting the full module path (e.g. foo.bar) of the calling module and comparing this with the module a symbol is in, and then do this sort of thing:
In a module like foolib.mod:
- If some API-marked function is called from any submodule of "foolib"
=> compilation proceeds
- Otherwise, statically assert.
However I've just realized we are missing a __MODULE__ equivalent which gives us the full path of the module. For example, for a.b.c returns c, rather than a.b.c.
Adam D. Ruppe will probably understand where I'm going with this. Just to pseudocode a bit:
test.d:
-----
module test;
import foo.a;
import foo.b;
void main()
{
B b;
b.func(); // this will fail
}
-----
foo/a.d:
-----
module foo.a;
import foo.b;
void test()
{
B b;
b.func(); // ok, compiles
}
-----
foo/b.d:
-----
module foo.b;
import foo.internal;
struct B
{
// introduce public function "func" only accessible from any 'foo'
submodules
// which forwards to "funcImpl"
mixin Internal!(funcImpl, "func");
private void funcImpl() { } // internal
}
-----
foo/internal.d:
-----
module foo.internal;
mixin template Internal(alias symbol, string funcIdent)
{
mixin("
public auto " ~ funcIdent ~ "(string packName = __PACKAGE__, T...)(T t)
{
// note: not the actual assert, you would probably do string
comparisons here,
// e.g. checking whether both packages start with "foo."
static assert(__PACKAGE__ == packName);
return symbol(t);
}
");
}
-----
The only issue is there's no way to get to the caller's fully qualified module name. You could hack around extracting this info from a __PRETTY_FUNCTION__ string, but it's unreliable.
I think we may need a new __PACKAGE__ symbol.
|
October 06, 2013 Re: My design need friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to Andrej Mitrovic | This is also nice. My final construct now looks like this: Friend: ---- module Core.Friend; struct Friend { public: immutable string FriendClass; immutable string FriendMethod; this(string friendClass, string friendMethod = null) { this.FriendClass = friendClass; this.FriendMethod = friendMethod; } } ---- Accessor: ---- module Core.Accessor; import std.stdio; import std.string : format; import Core.Friend; class FriendException : Exception { public: this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { super(msg, file, line, next); } } mixin template Accessor(T) { public: void friendCall(string method, Request, Args...)(ref const Request caller, Args args) { auto friends = __traits(getAttributes, T); immutable string ReqStr = __traits(identifier, Request); immutable string Tstr = __traits(identifier, T); static if (friends.length != 0 && is(typeof(friends[0]) == Friend)) { foreach (ref const Friend friend; friends) { if (friend.FriendClass == ReqStr) { if (friend.FriendMethod.length != 0 && method != friend.FriendMethod) { throw new FriendException(format("%s is a friend of %s but no friend of method %s.%s.", ReqStr, Tstr, Tstr, method)); } mixin("return this." ~ method ~ "(args);"); } } throw new FriendException(format("%s is not a friend of %s.", ReqStr, Tstr)); } else { throw new FriendException(format("%s has no friends.", Tstr)); } } } ---- And Drawable: ---- @Friend("Window", "render") interface Drawable { protected: void _render(); package: final void render() { this._render(); } public: mixin Accessor!Drawable; } ---- I'm in favor that something like that is really added to std.typecons. And I should write a blog post about your and my solution. :) |
October 06, 2013 Re: My design need friends | ||||
---|---|---|---|---|
| ||||
Posted in reply to Namespace | On 10/6/13, Namespace <rswhite4@googlemail.com> wrote:
> And I should write a blog post about your and my solution. :)
Let me try to hack on __PRETTY_FUNCTION__ first and I'll post a working example here soon.
|
Copyright © 1999-2021 by the D Language Foundation