2014-08-28 11 views
16

Ist es derzeit möglich, alle Funktionen (oder Klassen) mit einigen Attribut über Module hinweg zu scannen/abfragen/iterieren?D: Finden aller Funktionen mit bestimmten Attribut

Zum Beispiel:


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 ... */) { 
     ... 
    } 
} 

Antwort

37

Ob Sie es glauben oder nicht, aber ja, es ist irgendwie ... obwohl es wirklich Hacky ist und eine Menge Löcher aufweist. Code: http://arsdnet.net/d-walk/

Lauf, die gedruckt werden:

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

Sie werden einen kurzen Blick auf c.d, b.d, nehmen wollen und main.d zu sehen, die Nutzung. Die onEach Funktion in main.d verarbeitet jeden Treffer die Hilfsfunktion findet, druckt hier nur den Namen. In der main Funktion werden Sie eine verrückte Suche mixin(__MODULE__) sehen - das ist ein hacky Trick, um einen Verweis auf das aktuelle Modul als Ausgangspunkt für unsere Iteration zu erhalten.

Beachten Sie auch, dass die main.d Datei oben einen module project.main; Line-Up hat - wenn der Modulname nur main war, wie es automatisch ohne diese Erklärung ist, der mixin Hack würde das Modul für die Funktion main verwirren. Dieser Code ist wirklich spröde! Jetzt

, richten Sie Ihre Aufmerksamkeit auf attr.d: http://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; 
      } 
     } 
    } 
} 

Zuerst haben wir die Attributdefinition und einige Helfer ihre Präsenz zu erkennen. Wenn Sie UDAs zuvor verwendet haben, nichts wirklich neues hier - nur Scannen der Attribute Tupel für den Typ, den wir interessiert sind.

Die helper Vorlagen sind ein Trick, um wiederholte Anrufe zu __traits(getMember) zu kürzen - es alias es nur zu einem netterer Name unter Vermeidung eines dummen Parse-Fehlers im Compiler.

Endlich haben wir das Fleisch des Walker. Es dreht sich über allMembers, D kompilieren Zeit Reflexion Arbeitspferd (wenn Sie nicht damit vertraut sind, werfen Sie einen Blick auf das Beispiel Kapitel meiner D-Kochbuch https://www.packtpub.com/application-development/d-cookbook - die "kostenlose Probe" Link ist das Kapitel zur Kompilierzeit Reflexion)

Als nächstes stellt die erste static if sicher, dass wir tatsächlich das Mitglied bekommen können, das wir bekommen wollen. Andernfalls würden beim Versuch, private Mitglieder des automatisch importierten Moduls object zu erhalten, Fehler ausgegeben.

Das Ende der Funktion ist auch einfach - es ruft nur unsere onEach Sache auf jedem Element. Aber die Mitte ist, wo die Magie ist: wenn es ein Modul erkennt (sooo hacky übrigens, aber nur ich weiß es zu tun) importieren in den Spaziergang, importiert es hier, Zugriff auf es über die mixin(module) Trick auf der obersten Ebene verwendet ... rekursiv durch das Importdiagramm des Programms.

Wenn Sie herumspielen, werden Sie sehen, dass es irgendwie funktioniert. (Kompilieren all diese Dateien zusammen auf der Kommandozeile btw für beste Ergebnisse: dmd main.d attr.d b.d c.d)

Aber es hat auch eine Reihe von Einschränkungen:

  • in der Klasse gehen/struct Mitglieder sind möglich, aber nicht hier umgesetzt . Ziemlich einfach aber: Wenn das Mitglied eine Klasse ist, steigen Sie auch rekursiv in es ein.

  • Es kann beschädigt werden, wenn ein Modul einen Namen mit einem Element teilt, wie z. B. das oben genannte Beispiel mit main. Umgehen Sie mit eindeutigen Modulnamen mit einigen Paketpunkten auch, sollte in Ordnung sein.

  • Es wird nicht in funktional-lokale Importe absteigen, was bedeutet, dass es möglich ist, eine Funktion im Programm zu verwenden, die von diesem Trick nicht erfasst wird. Mir ist heute keine Lösung in D bekannt, auch wenn Sie bereit sind, jeden Hack in der Sprache zu benutzen.

  • Hinzufügen von Code mit UDAs ist immer schwierig, aber doppelt so, weil die onEach eine Funktion mit seinem on scope ist. Sie könnten vielleicht ein globales assoziatives Array von Delegaten in Handlern für die Dinge erstellen: void delegate()[string] handlers; /* ... */ handlers[memberName] = &localHandlerForThis; Art der Sache für den Laufzeitzugriff auf die Informationen.

  • Ich wette, es wird auch auf komplexeren Sachen nicht kompilieren, ich habe das jetzt zusammen als ein Spielzeug Proof of Concept geschlagen.

meist D-Code, anstatt zu versuchen, den Import Baum so zu gehen, verlangt nur, dass Sie mixin UdaHandler!T; in dem einzelnen Aggregate oder Modul, in dem es verwendet wird, z.B. mixin RegisterSerializableClass!MyClass; nach jedem. Vielleicht nicht super DRY, aber viel zuverlässiger.

bearbeiten: Es gibt einen anderen Fehler, den ich nicht bemerkte, wenn ich die Antwort ursprünglich schrieb: das "Modul b.d;" wurde nicht wirklich abgeholt. Umbenennung in "Modul b;" funktioniert, aber nicht, wenn es das Paket enthält.

ooooh cuz es gilt als "Paket mod" in stringof .... die keine Mitglieder hat. Vielleicht, wenn der Compiler es nur "Modul foo.bar" anstelle von "Paket foo" nannte, wären wir aber im Geschäft. (natürlich ist dies nicht praktisch für Anwendungsautoren ... was irgendwie die Nützlichkeit des Tricks zu dieser Zeit ruiniert)

+2

Irgendwie ist es einfach nicht genug, eine solche Antwort zu akzeptieren und zu wählen. Ich denke, diese Antwort ist mindestens 2-3 Pints ​​wert! –

+2

Für den Rekord, ich habe es am Ende mit Ihrem Vorschlag der Verwendung von Mixins ('Mixin SillWalk! (10, func)') implementiert. Aber deinen Code zu growing war eine sehr wertvolle pädagogische Erfahrung! –

+0

@Adam, wäre es gut, wenn Sie Ihre Antwort bearbeiten könnten, um den Code in den Text der Antwort aufzunehmen. –

Verwandte Themen