D:找到具有特定属性的所有函数

时间:2014-08-28 18:10:29

标签: d

目前是否可以跨模块扫描/查询/迭代所有具有某些属性的函数(或类)?

例如:


source/packageA/something.d:

@sillyWalk(10)
void doSomething()
{
}

source/packageB/anotherThing.d:

@sillyWalk(50)
void anotherThing()
{
}

source/main.d:

void main()
{
    for (func; /* All @sillWalk ... */) {
        ...
    }
}

1 个答案:

答案 0 :(得分:37)

信不信由你,但是,它有点......虽然它真的很hacky并且有很多漏洞。代码:http://arsdnet.net/d-walk/

正在运行将打印:

Processing: module main
Processing: module object
Processing: module c
Processing: module attr
test2() has sillyWalk
main() has sillyWalk

您希望快速查看c.db.dmain.d以查看用法。 onEach进程中的main.d函数每次点击帮助函数找到,这里只是打印名称。在main函数中,您会看到一个疯狂的mixin(__MODULE__) - 这是一个hacky技巧,可以将当前模块的引用作为迭代的起点。

另请注意,main.d文件顶部有module project.main;行 - 如果模块名称只是main,因为它是自动的,没有声明,mixin黑客会使模块混淆函数main。这段代码真的很脆弱!

现在,请注意attr.dhttp://arsdnet.net/d-walk/attr.d

module attr;

struct sillyWalk { int i; }

enum isSillyWalk(alias T) = is(typeof(T) == sillyWalk);

import std.typetuple;
alias hasSillyWalk(alias what) = anySatisfy!(isSillyWalk, __traits(getAttributes, what));
enum hasSillyWalk(what) = false;

alias helper(alias T) = T;
alias helper(T) = T;

void allWithSillyWalk(alias a, alias onEach)() {
    pragma(msg, "Processing: " ~ a.stringof);
    foreach(memberName; __traits(allMembers, a)) {
        // guards against errors from trying to access private stuff etc.
        static if(__traits(compiles, __traits(getMember, a, memberName))) {
            alias member = helper!(__traits(getMember, a, memberName));

            // pragma(msg, "looking at " ~ memberName);
            import std.string;
            static if(!is(typeof(member)) && member.stringof.startsWith("module ")) {
                enum mn = member.stringof["module ".length .. $];
                mixin("import " ~ mn ~ ";");
                allWithSillyWalk!(mixin(mn), onEach);
            }

            static if(hasSillyWalk!(member)) {
                onEach!member;
            }
        }
    }
}

首先,我们有属性定义和一些帮助来检测它的存在。如果您以前使用过UDA,那么这里没有什么新东西 - 只需扫描我们感兴趣的类型的属性元组。

helper模板是缩写重复调用__traits(getMember)的一种技巧 - 它只是将其别名化为更好的名称,同时避免编译器中出现愚蠢的解析错误。

最后,我们有步行者的肉。它循环遍历allMembers,D编译时反思的主力(如果你不熟悉这一点,请看看我的D Cookbook的样本章节https://www.packtpub.com/application-development/d-cookbook - " Free Sample"链接是关于编译时反射的章节)

接下来,第一个static if只是确保我们能够真正获得我们想要的成员。如果没有这个,它会在尝试获取自动导入的object模块的私有成员时抛出错误。

函数的结尾也很简单 - 它只是在每个元素上调用我们的onEach事物。但中间是神奇的地方:如果它检测到一个模块(sooo hacky btw但我只知道这样做)在walk中导入,它在这里导入它,通过使用的mixin(module)技巧获得访问权限在顶层...因此通过程序的导入图表进行递归。

如果你玩游戏,你会发现它确实有点有效。 (在命令行btw上一起编译所有这些文件以获得最佳结果:dmd main.d attr.d b.d c.d

但它也有许多限制:

  • 可以进入class / struct成员,但这里没有实现。但是很简单:如果成员是一个类,也可以递归地进入它。

  • 如果模块与成员共享名称,则可能会中断,例如上面提到的main示例。通过使用带有一些包点的唯一模块名称来解决这个问题应该没问题。

  • 它不会进入函数本地导入,这意味着可以在程序中使用一个不会被这个技巧拾取的函数。我今天不知道D中有任何解决方案,即使你愿意使用该语言中的每一个黑客也是如此。

  • 使用UDA添加代码总是很棘手,但这里要加倍,因为onEach是一个具有on范围的函数。您可以建立一个全局关联的委托数组到处理程序中,但是:void delegate()[string] handlers; /* ... */ handlers[memberName] = &localHandlerForThis;类似于运行时访问信息的东西。

  • 我认为它也无法在更复杂的东西上编译,我现在只是把它作为玩具的概念验证。

大多数D代码,而不是像这样尝试遍历导入树,只需要在单个聚合或模块中使用mixin UdaHandler!T;,例如:每个人之后mixin RegisterSerializableClass!MyClass;。也许不是超级DRY,但更可靠。

编辑: 还有一个在我最初写答案时没有注意到的错误:"模块b.d;"实际上并没有被接受。将其重命名为"模块b;"有效,但不包括包裹。

ooooh cuz它被认为是"包mod"在stringof ....没有成员。也许如果编译器只是调用它"模块foo.bar"而不是"包foo"我们虽然在做生意。 (当然这对于应用程序编写者来说并不实用......这有时会破坏这个技巧的实用性)