I am playing with this problem for my own education only, to learn more about dispatch and computations on the type system. Questions at the end.
Objective: implement something like findfirst((:a, :b, :c), x)
that would be compiled to very efficient code, using just the type system and dispatch, not generated functions. The first sequence is always a tuple of bits types so they can be type parameters.
What I have so far: a type
struct IX{T} end
IX(names::Symbol...) = IX{names}() # convenience constructor
ix1 = IX(:a, :b, :c)
A complicated system of dispatch using various levels:
# utility functions
_splitfirst(car, cdr...) = car
_splitrest(car, cdr...) = cdr
# no match, check rest
function _gx1{key,i,name,namerest}(::Type{Val{key}}, ::Type{Val{i}}, ::Type{Val{name}},
::Type{Val{namerest}})
_gx1(Val{key}, Val{i+1}, Val{_splitfirst(namerest...)}, Val{_splitrest(namerest...)})
end
# no match, no more items, return 0
_gx1{key,i,name}(::Type{Val{key}}, ::Type{Val{i}}, ::Type{Val{name}}, ::Type{Val{()}}) = 0
# match
_gx1{key,i}(::Type{Val{key}}, ::Type{Val{i}}, ::Type{Val{key}}, ::Type{Val{()}}) = i
# match, no more items -- BUT WHY DO I NEED THIS METHOD?
_gx1{key,i,namerest}(::Type{Val{key}}, ::Type{Val{i}}, ::Type{Val{key}},
::Type{Val{namerest}}) = i
# this just calls _gx1
_gx2{key}(::Type{Val{key}}, ::Type{Val{()}}) = 0
function _gx2{key,names}(::Type{Val{key}}, ::Type{Val{names}})
_gx1(Val{key}, Val{1}, Val{_splitfirst(names...)}, Val{_splitrest(names...)})
end
getindex{names}(::IX{names}, key) = _gx2(Val{key}, Val{names})
And some tests:
ix1[:a] # 1
ix1[:b] # 2
ix1[:c] # 3
ix1[:d] # 0
Using v0.6-rc1
.
Questions:
- why do I need the fourth
_gx1
method? I thought the third one would apply. Is this a bug? - can I do this in a simpler way?
- related: how can I check that it compiles to optimal code? inspecting
f(ix) = ix[:b]
@code_llvm f(ix1)
seems to suggest it is compiled to quite complex code.