Tricks.jl

Cunning tricks though the julia compiler internals
Author oxinabox
Popularity
20 Stars
Updated Last
6 Months Ago
Started In
September 2019

Tricks

Build Status Build Status Codecov Build Status

Tricks.jl is an experimental package that does tricks with the Julia edge system.

Currently it has 2 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 https://github.com/JuliaLang/julia/pull/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.

There is also a compatibility version of this function called compat_hasmethod, which picks between static_hasmethod or hasmethod depending on the Julia version. Use this method inside your package to be compatible with VERSION < v"1.3"

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.

Uses

We can use static_hasmethod to declare traits.

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(
1return $(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
2return $(QuoteNode(Iterable()))
3return $(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()

Required Packages

No packages found.