Static.jl
Static.jl provides a set of statically-parameterized types for dispatch that work seamlessly with dynamic values. These types enable compile-time optimizations while maintaining ergonomic interoperability with standard Julia types.
Overview
The package defines several static types:
TrueandFalse- Static booleansStaticInt{N}- Static integersStaticFloat64{N}- Static floating-point numbersStaticSymbol{S}- Static symbols
Unlike Base.Val, these types provide "static" values (known at compile time) that work interchangeably with dynamic values in many contexts.
Quick Start
using Static
# Create static values
x = static(1) # StaticInt{1}()
b = static(true) # True()
s = static(:my_symbol) # StaticSymbol{:my_symbol}()
# Convert back to dynamic
dynamic(static(1)) # 1
# Extract compile-time-known values from types
known(typeof(static(1))) # 1
known(typeof(1)) # nothingKey Functions
The package provides three core conversion functions:
static(x)- Convert a value to its static formdynamic(x)- Convert a static value back to its dynamic formknown(T)- Extract the compile-time-known value from a type
API Reference
Static.IntTypeStatic.NDIndexStatic.OptionallyStaticStepRangeStatic.OptionallyStaticUnitRangeStatic.SOneToStatic.SUnitRangeStatic.StaticBoolStatic.StaticFloat64Static.StaticIntStatic.StaticSymbolStatic.addStatic.dynamicStatic.eachopStatic.eachop_tupleStatic.eqStatic.eqStatic.field_typeStatic.geStatic.geStatic.gtStatic.gtStatic.is_staticStatic.knownStatic.leStatic.leStatic.ltStatic.ltStatic.mulStatic.neStatic.neStatic.reduce_tupStatic.staticStatic.static_promoteStatic.static_promote
Static.IntType — Type
IntType(x::Integer)::Union{Int, StaticInt}IntType is a union of Int and StaticInt. As a function, it ensures that x one of the two.
Static.NDIndex — Type
NDIndex(i, j, k...) -> I
NDIndex((i, j, k...)) -> IA multidimensional index that refers to a single element. Each dimension is represented by a single Int or StaticInt.
julia> using Static
julia> i = NDIndex(static(1), 2, static(3))
NDIndex(static(1), 2, static(3))
julia> i[static(1)]
static(1)
julia> i[1]
1
Static.OptionallyStaticStepRange — Type
OptionallyStaticStepRange(start, step, stop) <: OrdinalRange{Int,Int}Similarly to OptionallyStaticUnitRange, OptionallyStaticStepRange permits a combination of static and standard primitive Ints to construct a range. It specifically enables the use of ranges without a step size of 1. It may be constructed through the use of OptionallyStaticStepRange directly or using static integers with the range operator (i.e., :).
julia> using Static
julia> x = static(2);
julia> x:x:10
static(2):static(2):10
julia> Static.OptionallyStaticStepRange(x, x, 10)
static(2):static(2):10
Static.OptionallyStaticUnitRange — Type
OptionallyStaticUnitRange(start, stop) <: AbstractUnitRange{Int}Similar to UnitRange except each field may be an Int or StaticInt. An OptionallyStaticUnitRange is intended to be constructed internally from other valid indices. Therefore, users should not expect the same checks are used to ensure construction of a valid OptionallyStaticUnitRange as a UnitRange.
Static.SOneTo — Type
SOneTo(n::Int)An alias for OptionallyStaticUnitRange usfeul for statically sized axes.
Static.SUnitRange — Type
SUnitRange(start::Int, stop::Int)An alias for OptionallyStaticUnitRange where both the start and stop are known statically.
Static.StaticBool — Type
StaticBool(x::Bool)::Union{True, False}A statically typed Bool.
Static.StaticFloat64 — Type
StaticFloat64(F::Float64)::StaticFloat64{F}A statically sized Float64. Use StaticFloat64(N) instead of Val(N) when you want it to behave like a number.
Static.StaticInt — Type
StaticInt(N::Int)::StaticInt{N}A statically sized Int. Use StaticInt(N) instead of Val(N) when you want it to behave like a number.
Static.StaticSymbol — Type
StaticSymbol(S::Symbol)::StaticSymbol{S}A statically typed Symbol.
Static.add — Method
Static.add(x) -> Base.Fix2(+, x)
Static.add(x, y)Create a function that adds x to other values (i.e. a function equivalent to y -> y + x).
Static.dynamic — Method
dynamic(x)Returns the "dynamic" or non-static form of x. If x is not a static type, then it is returned unchanged.
dynamic ensures that the type of the returned value is always inferred, even if the compiler fails to infer the exact value.
See also: known
Examples
julia> dynamic(static(1))
1
julia> dynamic(1)
1
Static.eachop — Method
Static.eachop(op, args...; iterator::Tuple{Vararg{StaticInt}})::TupleProduces a tuple of (op(args..., iterator[1]), op(args..., iterator[2]),...).
Static.eachop_tuple — Method
Static.eachop_tuple(op, arg, args...; iterator::Tuple{Vararg{StaticInt}})::Type{Tuple}Produces a tuple type of Tuple{op(arg, args..., iterator[1]), op(arg, args..., iterator[2]),...}. Note that if one of the arguments passed to op is a Tuple type then it should be the first argument instead of one of the trailing arguments, ensuring type inference of each element of the tuple.
Static.field_type — Method
field_type(::Type{T}, f)Functionally equivalent to fieldtype(T, f) except f may be a static type.
Static.is_static — Method
is_static(::Type{T})::Union{True, False}If T is a static type return static(true)::True and otherwise returns static(false)::False
Static.known — Method
known(T::Type)Returns the known value corresponding to a static type T. If T is not a static type then nothing is returned.
known ensures that the type of the returned value is always inferred, even if the compiler fails to infer the exact value.
See also: static, is_static, dynamic
Examples
julia> known(StaticInt{1})
1
julia> known(Int)
Static.mul — Method
Static.mul(x)::Base.Fix2{typeof(*)}Create a function that multiplies x with other values (i.e. a function equivalent to y -> y * x).
Static.reduce_tup — Method
reduce_tup(f::F, inds::Tuple{Vararg{Any,N}}) where {F,N}
An optimized reduce for tuples. Base.reduce's afoldl will often not inline. Additionally, reduce_tup attempts to order the reduction in an optimal manner.
julia> using StaticArrays, Static, BenchmarkTools
julia> rsum(v::SVector) = Static.reduce_tup(+, v.data)
rsum (generic function with 2 methods)
julia> for n ∈ 2:16
@show n
v = @SVector rand(n)
s1 = @btime sum($(Ref(v))[])
s2 = @btime rsum($(Ref(v))[])
end
n = 2
0.863 ns (0 allocations: 0 bytes)
0.863 ns (0 allocations: 0 bytes)
n = 3
0.862 ns (0 allocations: 0 bytes)
0.863 ns (0 allocations: 0 bytes)
n = 4
0.862 ns (0 allocations: 0 bytes)
0.862 ns (0 allocations: 0 bytes)
n = 5
1.074 ns (0 allocations: 0 bytes)
0.864 ns (0 allocations: 0 bytes)
n = 6
0.864 ns (0 allocations: 0 bytes)
0.862 ns (0 allocations: 0 bytes)
n = 7
1.075 ns (0 allocations: 0 bytes)
0.864 ns (0 allocations: 0 bytes)
n = 8
1.077 ns (0 allocations: 0 bytes)
0.865 ns (0 allocations: 0 bytes)
n = 9
1.081 ns (0 allocations: 0 bytes)
0.865 ns (0 allocations: 0 bytes)
n = 10
1.195 ns (0 allocations: 0 bytes)
0.867 ns (0 allocations: 0 bytes)
n = 11
1.357 ns (0 allocations: 0 bytes)
1.400 ns (0 allocations: 0 bytes)
n = 12
1.543 ns (0 allocations: 0 bytes)
1.074 ns (0 allocations: 0 bytes)
n = 13
1.702 ns (0 allocations: 0 bytes)
1.077 ns (0 allocations: 0 bytes)
n = 14
1.913 ns (0 allocations: 0 bytes)
0.867 ns (0 allocations: 0 bytes)
n = 15
2.076 ns (0 allocations: 0 bytes)
1.077 ns (0 allocations: 0 bytes)
n = 16
2.273 ns (0 allocations: 0 bytes)
1.078 ns (0 allocations: 0 bytes)More importantly, reduce_tup(_pick_range, inds) often performs better than reduce(_pick_range, inds).
julia> using ArrayInterface, BenchmarkTools, Static
julia> inds = (Base.OneTo(100), 1:100, 1:static(100))
(Base.OneTo(100), 1:100, 1:static(100))
julia> @btime reduce(ArrayInterface._pick_range, $(Ref(inds))[])
6.405 ns (0 allocations: 0 bytes)
Base.Slice(static(1):static(100))
julia> @btime Static.reduce_tup(ArrayInterface._pick_range, $(Ref(inds))[])
2.570 ns (0 allocations: 0 bytes)
Base.Slice(static(1):static(100))
julia> inds = (Base.OneTo(100), 1:100, 1:UInt(100))
(Base.OneTo(100), 1:100, 0x0000000000000001:0x0000000000000064)
julia> @btime reduce(ArrayInterface._pick_range, $(Ref(inds))[])
6.411 ns (0 allocations: 0 bytes)
Base.Slice(static(1):100)
julia> @btime Static.reduce_tup(ArrayInterface._pick_range, $(Ref(inds))[])
2.592 ns (0 allocations: 0 bytes)
Base.Slice(static(1):100)
julia> inds = (Base.OneTo(100), 1:100, 1:UInt(100), Int32(1):Int32(100))
(Base.OneTo(100), 1:100, 0x0000000000000001:0x0000000000000064, 1:100)
julia> @btime reduce(ArrayInterface._pick_range, $(Ref(inds))[])
9.048 ns (0 allocations: 0 bytes)
Base.Slice(static(1):100)
julia> @btime Static.reduce_tup(ArrayInterface._pick_range, $(Ref(inds))[])
2.569 ns (0 allocations: 0 bytes)
Base.Slice(static(1):100)Static.static — Method
static(x)Returns a static form of x. If x is already in a static form then x is returned. If there is no static alternative for x then an error is thrown.
Examples
julia> using Static
julia> static(1)
static(1)
julia> static(true)
True()
julia> static(:x)
static(:x)
Static.static_promote — Method
static_promote(x::AbstractRange{<:Integer}, y::AbstractRange{<:Integer})A type stable method for combining two equal ranges into a new range that preserves static parameters. Throws an error if x != y.
Examples
julia> static_promote(static(1):10, 1:static(10))
static(1):static(10)
julia> static_promote(1:2:9, static(1):static(2):static(9))
static(1):static(2):static(9)Static.static_promote — Method
static_promote(x, y)Throws an error if x and y are not equal, preferentially returning the one that is known at compile time.