Reflection and introspection

Julia bietet eine Vielzahl von Laufzeit-Reflexionsfähigkeiten.

Module bindings

Die öffentlichen Namen für ein Module sind verfügbar über names(m::Module), was ein Array von Symbol Elementen zurückgibt, die die öffentlichen Bindungen darstellen. names(m::Module, all = true) gibt Symbole für alle Bindungen in m zurück, unabhängig vom öffentlichen Status.

DataType fields

Die Namen der DataType-Felder können mit fieldnames abgefragt werden. Zum Beispiel gibt der folgende Typ fieldnames(Point) ein Tupel von Symbols zurück, die die Feldnamen darstellen:

julia> struct Point
           x::Int
           y
       end

julia> fieldnames(Point)
(:x, :y)

Der Typ jedes Feldes in einem Point-Objekt wird im types-Feld der Point-Variablen selbst gespeichert:

julia> Point.types
svec(Int64, Any)

Während x als Int annotiert ist, wurde y in der Typdefinition nicht annotiert, daher wird y standardmäßig vom Typ Any.

Typen werden selbst als eine Struktur namens DataType dargestellt:

julia> typeof(Point)
DataType

Beachten Sie, dass fieldnames(DataType) die Namen für jedes Feld von DataType selbst zurückgibt, und eines dieser Felder ist das types-Feld, das im obigen Beispiel beobachtet wurde.

Subtypes

Die direkten Subtypen eines beliebigen DataType können mit subtypes aufgelistet werden. Zum Beispiel hat der abstrakte DataType AbstractFloat vier (konkrete) Subtypen:

julia> InteractiveUtils.subtypes(AbstractFloat)
5-element Vector{Any}:
 BigFloat
 Core.BFloat16
 Float16
 Float32
 Float64

Jeder abstrakte Subtyp wird ebenfalls in dieser Liste enthalten sein, aber weitere Subtypen davon nicht; die rekursive Anwendung von subtypes kann verwendet werden, um den vollständigen Typbaum zu inspizieren.

Beachten Sie, dass subtypes sich innerhalb von InteractiveUtils befindet, aber automatisch exportiert wird, wenn das REPL verwendet wird.

DataType layout

Die interne Darstellung eines DataType ist entscheidend, wenn man mit C-Code interagiert, und mehrere Funktionen stehen zur Verfügung, um diese Details zu inspizieren. isbitstype(T::DataType) gibt true zurück, wenn T mit C-kompatibler Ausrichtung gespeichert ist. fieldoffset(T::DataType, i::Integer) gibt den (Byte-)Offset für das Feld i relativ zum Beginn des Typs zurück.

Function methods

Die Methoden einer beliebigen Funktion können mit methods aufgelistet werden. Die Methodenzuordnungstabelle kann nach Methoden durchsucht werden, die einen bestimmten Typ akzeptieren, mit methodswith.

Expansion and lowering

Wie im Abschnitt Metaprogramming besprochen, gibt die Funktion macroexpand den unquoted und interpolierten Ausdruck (Expr) für ein gegebenes Makro zurück. Um macroexpand zu verwenden, muss der Ausdrucksblock selbst quote-iert werden (ansonsten wird das Makro ausgewertet und das Ergebnis wird stattdessen übergeben!). Zum Beispiel:

julia> InteractiveUtils.macroexpand(@__MODULE__, :(@edit println("")) )
:(InteractiveUtils.edit(println, (Base.typesof)("")))

Die Funktionen Base.Meta.show_sexpr und dump werden verwendet, um S-expr-Stilansichten und tief verschachtelte Detailansichten für beliebige Ausdrücke anzuzeigen.

Schließlich gibt die Meta.lower-Funktion die lowered-Form eines beliebigen Ausdrucks zurück und ist von besonderem Interesse, um zu verstehen, wie Sprachkonstrukte auf primitive Operationen wie Zuweisungen, Verzweigungen und Aufrufe abgebildet werden:

julia> Meta.lower(@__MODULE__, :( [1+2, sin(0.5)] ))
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope`
1 ─ %1 = 1 + 2
│   %2 = sin(0.5)
│   %3 = Base.vect(%1, %2)
└──      return %3
))))

Intermediate and compiled representations

Die Inspektion der gesenkten Form für Funktionen erfordert die Auswahl der spezifischen Methode zur Anzeige, da generische Funktionen viele Methoden mit unterschiedlichen Typ-Signaturen haben können. Zu diesem Zweck ist eine methodenspezifische Code-Senkung verfügbar, die mit code_lowered verwendet werden kann, und die typ-inferierte Form ist verfügbar mit code_typed. code_warntype fügt der Ausgabe von 4d61726b646f776e2e436f64652822222c2022636f64655f74797065642229_40726566 Hervorhebungen hinzu.

Näher an der Maschine kann die LLVM-Zwischenrepräsentation einer Funktion mit code_llvm ausgegeben werden, und schließlich ist der kompilierte Maschinencode mit code_native verfügbar (dies löst die JIT-Kompilierung/code-Generierung für jede Funktion aus, die zuvor nicht aufgerufen wurde).

Zur Vereinfachung gibt es Makroversionen der oben genannten Funktionen, die Standardfunktionsaufrufe übernehmen und die Argumenttypen automatisch erweitern:

julia> @code_llvm +(1,1)
;  @ int.jl:87 within `+`
; Function Attrs: sspstrong uwtable
define i64 @"julia_+_476"(i64 signext %0, i64 signext %1) #0 {
top:
  %2 = add i64 %1, %0
  ret i64 %2
}

Für weitere Informationen siehe @code_lowered, @code_typed, @code_warntype, @code_llvm, und @code_native.

Printing of debug information

Die oben genannten Funktionen und Makros nehmen das Schlüsselwortargument debuginfo, das das Niveau der ausgegebenen Debug-Informationen steuert.

julia> InteractiveUtils.@code_typed debuginfo=:source +(1,1)
CodeInfo(
    @ int.jl:87 within `+`
1 ─ %1 = Base.add_int(x, y)::Int64
└──      return %1
) => Int64

Mögliche Werte für debuginfo sind: :none, :source und :default. Standardmäßig werden keine Debug-Informationen ausgegeben, dies kann jedoch geändert werden, indem Base.IRShow.default_debuginfo[] = :source gesetzt wird.