Hi all,
I am developing a library where I would like to split some structs into, say, two categories based on their type; this is better shown as a minimal example:
abstract type Vowel end
abstract type Consonant end
struct A <: Vowel end
struct B <: Consonant end
struct C <: Consonant end
function sieve(letters...)
vowels = tuple((s for s in letters if s isa Vowel)...)
consonants = tuple((s for s in letters if s isa Consonant)...)
vowels, consonants
end
sieve(A(), B(), C(), A()) |> typeof
#output
Tuple{Tuple{A}, Tuple{B, C}}
The function above works; if I call it with only consonants, it is type-stable and everything is done at compile-time:
julia> @code_warntype sieve(B(), C())
MethodInstance for sieve(::B, ::C)
from sieve(letters...) @ Main REPL[6]:1
Arguments
#self#::Core.Const(sieve)
letters::Core.Const((B(), C()))
Locals
#4::var"#4#6"
#3::var"#3#5"
consonants::Tuple{B, C}
vowels::Tuple{}
Body::Tuple{Tuple{}, Tuple{B, C}}
1 ─ %1 = Main.tuple::Core.Const(tuple)
│ (#3 = %new(Main.:(var"#3#5")))
│ %3 = #3::Core.Const(var"#3#5"())
│ %4 = Base.Filter(%3, letters)::Core.Const(Base.Iterators.Filter{var"#3#5", Tuple{B, C}}(var"#3#5"(), (B(), C())))
│ %5 = Base.Generator(Base.identity, %4)::Core.Const(Base.Generator{Base.Iterators.Filter{var"#3#5", Tuple{B, C}}, typeof(identity)}(identity, Base.Iterators.Filter{var"#3#5", Tuple{B, C}}(var"#3#5"(), (B(), C()))))
│ (vowels = Core._apply_iterate(Base.iterate, %1, %5))
│ %7 = Main.tuple::Core.Const(tuple)
│ (#4 = %new(Main.:(var"#4#6")))
│ %9 = #4::Core.Const(var"#4#6"())
│ %10 = Base.Filter(%9, letters)::Core.Const(Base.Iterators.Filter{var"#4#6", Tuple{B, C}}(var"#4#6"(), (B(), C())))
│ %11 = Base.Generator(Base.identity, %10)::Core.Const(Base.Generator{Base.Iterators.Filter{var"#4#6", Tuple{B, C}}, typeof(identity)}(identity, Base.Iterators.Filter{var"#4#6", Tuple{B, C}}(var"#4#6"(), (B(), C()))))
│ (consonants = Core._apply_iterate(Base.iterate, %7, %11))
│ %13 = Core.tuple(vowels, consonants)::Core.Const(((), (B(), C())))
└── return %13
The return type shows Core.Const(((), (B(), C())))
- and this is quite impressive already! We have all necessary information at compile-time, as the function arguments are collected into a tuple letters::Core.Const((B(), C()))
; however, once a vowel is introduced, it all becomes type-unstable:
julia> @code_warntype sieve(A(), B(), C())
MethodInstance for sieve(::A, ::B, ::C)
from sieve(letters...) @ Main REPL[6]:1
Arguments
#self#::Core.Const(sieve)
letters::Core.Const((A(), B(), C()))
Locals
#4::var"#4#6"
#3::var"#3#5"
consonants::Tuple{Vararg{Union{A, B, C}}}
vowels::Tuple{A, Vararg{Union{A, B, C}}}
Body::Tuple{Tuple{A, Vararg{Union{A, B, C}}}, Tuple{Vararg{Union{A, B, C}}}}
1 ─ %1 = Main.tuple::Core.Const(tuple)
│ (#3 = %new(Main.:(var"#3#5")))
│ %3 = #3::Core.Const(var"#3#5"())
│ %4 = Base.Filter(%3, letters)::Core.Const(Base.Iterators.Filter{var"#3#5", Tuple{A, B, C}}(var"#3#5"(), (A(), B(), C())))
│ %5 = Base.Generator(Base.identity, %4)::Core.Const(Base.Generator{Base.Iterators.Filter{var"#3#5", Tuple{A, B, C}}, typeof(identity)}(identity, Base.Iterators.Filter{var"#3#5", Tuple{A, B, C}}(var"#3#5"(), (A(), B(), C()))))
│ (vowels = Core._apply_iterate(Base.iterate, %1, %5))
│ %7 = Main.tuple::Core.Const(tuple)
│ (#4 = %new(Main.:(var"#4#6")))
│ %9 = #4::Core.Const(var"#4#6"())
│ %10 = Base.Filter(%9, letters)::Core.Const(Base.Iterators.Filter{var"#4#6", Tuple{A, B, C}}(var"#4#6"(), (A(), B(), C())))
│ %11 = Base.Generator(Base.identity, %10)::Core.Const(Base.Generator{Base.Iterators.Filter{var"#4#6", Tuple{A, B, C}}, typeof(identity)}(identity, Base.Iterators.Filter{var"#4#6", Tuple{A, B, C}}(var"#4#6"(), (A(), B(), C()))))
│ (consonants = Core._apply_iterate(Base.iterate, %7, %11))
│ %13 = Core.tuple(vowels, consonants)::Tuple{Tuple{A, Vararg{Union{A, B, C}}}, Tuple{Vararg{Union{A, B, C}}}}
└── return %13
Now we do all type filtering at runtime and return an unknown Tuple{Tuple{A, Vararg{Union{A, B, C}}}, Tuple{Vararg{Union{A, B, C}}}}
.
Is there any way to do this filtering at compile-time? In the actual application this would help a lot with type-stability around the function call.
Thanks,
Leonard