Tricks
Tricks.jl is an particularly evil cunning package that does tricks with the Julia edge system.
Currently it has the following tricks:
static_hasmethod
.
This is like hasmethod
but it does not trigger any dynamic lookup of the method table.
It just returns the constant true
or false
.
If methods are added, recompilation is triggered.
This is based on JuliaLang/julia#32732 and that thread should be read before use.
If you can make a reproducible case of static_hasmethod
not working please post in #2.
I think it can't actually happen, and can't actually be called dynamically in a way that breaks it.
static_methods
This is just like methods
, but again it doesn't trigger any dynamic lookup of the method tables.
If you can make a reproducible case of static_methods
not working please open an issue.
static_fieldnames
, static_fieldtypes
, static_fieldcount
Just like Base.fieldnames
Base.fieldtypes
, and Base.fieldcount
but will participate in constant
propagation and will be free of runtime dynamism.
Uses
static_hasmethod
to declare traits.
We can use For demonstration we include versions based on static and nonstatic has_method
.
julia> using Tricks: static_hasmethod
julia> struct Iterable end; struct NonIterable end;
julia> iterableness_dynamic(::Type{T}) where T = hasmethod(iterate, Tuple{T}) ? Iterable() : NonIterable()
iterableness_dynamic (generic function with 1 method)
julia> iterableness_static(::Type{T}) where T = static_hasmethod(iterate, Tuple{T}) ? Iterable() : NonIterable()
iterableness_static (generic function with 1 method)
Demo:
julia> using BenchmarkTools
julia> const examples = (:a, "abc", [1,2,3], rand, (2,3), ones(4,10,2), 'a', 1:100);
julia> @btime [iterableness_dynamic(typeof(x)) for x in $examples]
13.608 μs (5 allocations: 304 bytes)
8-element Array{Any,1}:
NonIterable()
Iterable()
Iterable()
NonIterable()
Iterable()
Iterable()
Iterable()
Iterable()
julia> @btime [iterableness_static(typeof(x)) for x in $examples]
582.249 ns (5 allocations: 304 bytes)
8-element Array{Any,1}:
NonIterable()
Iterable()
Iterable()
NonIterable()
Iterable()
Iterable()
Iterable()
Iterable()
So it is over 20x faster.
this is because doesn't generate any code that has to run at runtime: (i.e. it is not dynamic)
julia> @code_typed iterableness_static(String)
CodeInfo(
1 ─ return $(QuoteNode(Iterable()))
) => Iterable
julia> @code_typed iterableness_dynamic(String)
CodeInfo(
1 ─ %1 = $(Expr(:foreigncall, :(:jl_gf_invoke_lookup), Any, svec(Any, UInt64), 0, :(:ccall), Tuple{typeof(iterate),String}, 0xffffffffffffffff, 0xffffffffffffffff))::Any
│ %2 = (%1 === Base.nothing)::Bool
│ %3 = Core.Intrinsics.not_int(%2)::Bool
└── goto #3 if not %3
2 ─ return $(QuoteNode(Iterable()))
3 ─ return $(QuoteNode(NonIterable()))
) => Union{Iterable, NonIterable}
Demonstration of it updating:
julia> struct Foo end
julia> iterableness_static(Foo)
NonIterable()
Initially, it wasn't iterable, but now we will add the iteration methods to it:
julia> Base.iterate(::Foo) = ("Foo", nothing);
julia> Base.iterate(::Foo, ::Nothing) = nothing;
julia> Base.length(::Foo) = 1;
julia> collect(Foo())
1-element Array{Any,1}:
"Foo"
julia> iterableness_static(Foo)
Iterable()
Julia version support
The core trick that Tricks.jl relies on was introduced in Julia 1.3. As such most of its methods do not work on earlier julia versions.
For compatability purposes we do provide:
compat_hasmethod
, which picks betweenstatic_hasmethod
orhasmethod
depending on the Julia version.