Reflection and introspection

Julia proporciona una variedad de capacidades de reflexión en tiempo de ejecución.

Module bindings

Los nombres públicos para un Module están disponibles usando names(m::Module), que devolverá un array de elementos Symbol que representan los enlaces públicos. names(m::Module, all = true) devuelve símbolos para todos los enlaces en m, independientemente del estado público.

DataType fields

Los nombres de los campos de DataType pueden ser interrogados usando fieldnames. Por ejemplo, dado el siguiente tipo, fieldnames(Point) devuelve una tupla de Symbol que representan los nombres de los campos:

julia> struct Point
           x::Int
           y
       end

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

El tipo de cada campo en un objeto Point se almacena en el campo types de la variable Point misma:

julia> Point.types
svec(Int64, Any)

Mientras x está anotado como un Int, y no fue anotado en la definición de tipo, por lo tanto, y por defecto es del tipo Any.

Los tipos se representan a sí mismos como una estructura llamada DataType:

julia> typeof(Point)
DataType

Tenga en cuenta que fieldnames(DataType) da los nombres de cada campo de DataType en sí, y uno de estos campos es el campo types observado en el ejemplo anterior.

Subtypes

Los subtipos directos de cualquier DataType pueden ser listados usando subtypes. Por ejemplo, el DataType abstracto AbstractFloat tiene cuatro (concretos) subtipos:

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

Cualquier subtipo abstracto también se incluirá en esta lista, pero no se incluirán subtipos adicionales de los mismos; se puede utilizar la aplicación recursiva de subtypes para inspeccionar el árbol de tipos completo.

Tenga en cuenta que subtypes se encuentra dentro de InteractiveUtils, pero se exporta automáticamente al usar el REPL.

DataType layout

La representación interna de un DataType es críticamente importante al interactuar con código C y varias funciones están disponibles para inspeccionar estos detalles. isbitstype(T::DataType) devuelve verdadero si T se almacena con alineación compatible con C. fieldoffset(T::DataType, i::Integer) devuelve el desplazamiento (en bytes) para el campo i relativo al inicio del tipo.

Function methods

Los métodos de cualquier función genérica pueden ser listados usando methods. La tabla de despacho de métodos puede ser buscada para métodos que acepten un tipo dado usando methodswith.

Expansion and lowering

Como se discutió en la sección Metaprogramming, la función macroexpand proporciona la expresión no citada e interpolada (Expr) en forma para un macro dado. Para usar macroexpand, cita el bloque de expresión en sí (¡de lo contrario, el macro será evaluado y el resultado será pasado en su lugar!). Por ejemplo:

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

Las funciones Base.Meta.show_sexpr y dump se utilizan para mostrar vistas en estilo S-exp y vistas de detalles anidadas en profundidad para cualquier expresión.

Finalmente, la función Meta.lower proporciona la forma lowered de cualquier expresión y es de particular interés para entender cómo los constructos del lenguaje se mapean a operaciones primitivas como asignaciones, ramas y llamadas:

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

Inspeccionar la forma reducida para funciones requiere la selección del método específico a mostrar, porque las funciones genéricas pueden tener muchos métodos con diferentes firmas de tipo. Para este propósito, el código de reducción específico del método está disponible usando code_lowered, y la forma inferida por tipo está disponible usando code_typed. code_warntype añade resaltado a la salida de 4d61726b646f776e2e436f64652822222c2022636f64655f74797065642229_40726566.

Más cerca de la máquina, la representación intermedia de LLVM de una función se puede imprimir usando code_llvm, y finalmente el código de máquina compilado está disponible usando code_native (esto activará la compilación JIT/generación de código para cualquier función que no haya sido llamada previamente).

Para conveniencia, hay versiones macro de las funciones anteriores que toman llamadas de función estándar y expanden los tipos de argumentos automáticamente:

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
}

Para más información, consulte @code_lowered, @code_typed, @code_warntype, @code_llvm, y @code_native.

Printing of debug information

Las funciones y macros mencionadas anteriormente toman el argumento de palabra clave debuginfo que controla el nivel de información de depuración impresa.

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

Los valores posibles para debuginfo son: :none, :source y :default. Por defecto, la información de depuración no se imprime, pero eso se puede cambiar configurando Base.IRShow.default_debuginfo[] = :source.