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:

  • True and False - Static booleans
  • StaticInt{N} - Static integers
  • StaticFloat64{N} - Static floating-point numbers
  • StaticSymbol{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))           # nothing

Key Functions

The package provides three core conversion functions:

  • static(x) - Convert a value to its static form
  • dynamic(x) - Convert a static value back to its dynamic form
  • known(T) - Extract the compile-time-known value from a type

API Reference

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.

source
Static.OptionallyStaticRange — Type
Static.OptionallyStaticRange{F, L}

A union of optionally static unit and step ranges whose start has type F and whose stop has type L.

Examples

julia> using Static

julia> r = static(1):static(2):static(5)
static(1):static(2):static(5)

julia> r isa Static.OptionallyStaticRange{StaticInt{1}, StaticInt{5}}
true
source
Static.False — Type
False()

The static representation of false.

Examples

julia> using Static

julia> False() === static(false)
true

julia> dynamic(False())
false
source
Static.NDIndex — Type
NDIndex(i, j, k...)   -> I
NDIndex((i, j, k...)) -> I

A 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
source
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
source
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.

source
Static.SOneTo — Type
SOneTo(n::Int)

An alias for OptionallyStaticUnitRange useful for statically sized axes.

source
Static.SUnitRange — Type
SUnitRange(start::Int, stop::Int)

An alias for OptionallyStaticUnitRange where both the start and stop are known statically.

source
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.

source
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.

source
Static.True — Type
True()

The static representation of true.

Examples

julia> using Static

julia> True() === static(true)
true

julia> dynamic(True())
true
source
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).

source
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
source
Static.eachop — Method
Static.eachop(op, args...; iterator::Tuple{Vararg{StaticInt}})::Tuple

Produces a tuple of (op(args..., iterator[1]), op(args..., iterator[2]),...).

source
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.

source
Static.eq — Method
Static.eq(x)::Base.Fix2{typeof(Static.eq)}

Create a function that compares x to other values using Static.eq (i.e. a function equivalent to y -> Static.eq(y, x)).

source
Static.eq — Method
Static.eq(x, y)::Union{Bool, True, False}

Equivalent to == but if x and y are static the return value is a StaticBool.

source
Static.field_type — Method
field_type(::Type{T}, f)

Functionally equivalent to fieldtype(T, f) except f may be a static type.

source
Static.ge — Method
Static.ge(x)::Base.Fix2{typeof(Static.ge)}

Create a function that compares x to other values using Static.ge (i.e. a function equivalent to y -> Static.ge(y, x)).

source
Static.ge — Method
Static.ge(x, y)::Union{Bool, True, False}

Equivalent to >= but if x and y are static the return value is a StaticBool.

source
Static.gt — Method
Static.gt(x)::Base.Fix2{typeof(Static.gt)}

Create a function that compares x to other values using Static.gt (i.e. a function equivalent to y -> Static.gt(y, x)).

source
Static.gt — Method
Static.gt(x, y)::Union{Bool, True, False}

Equivalent to > but if x and y are static the return value is a StaticBool.

source
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

See also: static, known

source
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)
source
Static.le — Method
Static.le(x)::Base.Fix2{typeof(Static.le)}

Create a function that compares x to other values using Static.le (i.e. a function equivalent to y -> Static.le(y, x)).

source
Static.le — Method
Static.le(x, y)::Union{Bool, True, False}

Equivalent to <= but if x and y are static the return value is a StaticBool.

source
Static.lt — Method
Static.lt(x)::Base.Fix2{typeof(Static.lt)}

Create a function that compares x to other values using Static.lt (i.e. a function equivalent to y -> Static.lt(y, x)).

source
Static.lt — Method
Static.lt(x, y)::Union{Bool, True, False}

Equivalent to < but if x and y are static the return value is a StaticBool.

source
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).

source
Static.ne — Method
Static.ne(x)::Base.Fix2{typeof(Static.ne)}

Create a function that compares x to other values using Static.ne (i.e. a function equivalent to y -> Static.ne(y, x)).

source
Static.ne — Method
Static.ne(x, y)::Union{Bool, True, False}

Equivalent to != but if x and y are static the return value is a StaticBool.

source
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)
source
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.

See also: is_static, known

Examples

julia> using Static

julia> static(1)
static(1)

julia> static(true)
True()

julia> static(:x)
static(:x)
source
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)
source
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.

source