I wonder if you’ve tried with the named tuple implementation in Keys.jl (v0.0.1, not master). That’s built on tuples.
I’ve just tried, and it actually seems worse since for some reason inference isn’t able to figure out the type of x.a
. Anyway, I don’t think it could change anything as the problem applies to all types due to invariance. Tuples are the exception since they are covariant, but as soon as you wrap them inside a struct
with type parameters the problem reappears.
Hmm, I worked pretty hard getting it to be type stable. Oh well worth a try.
Oh wait if you’re on v0.0.1 its pre dot overloading. You access values like this:
x[@key a]
I was using master.
Ok, well, this seems like it works on 0.7
struct Key{T <: Any} end
Key(x) = Key{x}()
Base.@pure argtail(x, rest...) = rest
tail(x) = argtail(x...)
Base.show(io::IO, ::Key{T}) where T = begin
print(io, ".")
print(io, T)
end
next_item(t::Tuple, reference_key::T, next_key::T) where T <: Key =
first(t)
next_item(t::Tuple{}, reference_key::T, next_key::T) where T <: Key =
error("No item after key $reference_key")
next_item(t::Tuple, reference_key::Key, next_key) =
get_key(t, reference_key)
next_item(t::Tuple{}, reference_key::Key, next_key) =
error("Key $reference_key not found")
get_key(t::Tuple, k::Key) = next_item(tail(t), k, first(t))
get_key(t::Tuple{}, k::Key) = error("Can't index an empty tuple")
Base.getproperty(t::Tuple, k::Symbol) = get_key(t, Key(k))
function test()
x = (Key(:a), 1, Key(:b), 2)
x.b
end
@code_warntype test()
Involves a bit of type piracy, but
Ok, sorry I keep making edits, but the implementation above is built on a plain tuple so maybe it could get around the issues
Maybe this implementation is slightly nicer:
struct Key{K} end
Key(s::Symbol) = Key{s}()
function Base.show(io::IO, k::Key{K}) where {K}
print(io, ".")
print(io, K)
end
struct Keyed{K, V}
value::V
end
Base.@pure Keyed(key::Symbol, value::V) where V = Keyed{key, V}(value)
function Base.show(io::IO, k::Keyed{K, V}) where {K, V}
print(io, K)
print(io, " = ")
print(io, k.value)
end
Base.@pure argtail(x, rest...) = rest
tail(x) = argtail(x...)
key_is(keyed::Keyed{K, V}, key::Key{K}) where {K, V} = true
key_is(any, k::Key) = false
value(k::Keyed) = k.value
Base.getproperty(t::Tuple, s::Symbol) = get_key(t, Key(s))
get_key(t::Tuple{}, k::Key) = error("Key $k not found")
function get_key(t::Tuple, k::Key) where {K, V}
first_item = first(t)
if key_is(first(t), k)
value(first_item)
else
get_key(tail(t), k)
end
end
function test()
x = (Keyed(:a, 1), Keyed(:b, "b"))
x.a
end
@code_warntype test()
Here’s another possible implementation:
struct Key{K} end
Key(s::Symbol) = Key{s}()
function Base.show(io::IO, k::Key{K}) where {K}
print(io, ".")
print(io, K)
end
struct Keyed{K, V}
value::V
end
Base.@pure Keyed(key::Symbol, value::V) where V = Keyed{key, V}(value)
function Base.show(io::IO, k::Keyed{K, V}) where {K, V}
print(io, K)
print(io, " = ")
print(io, k.value)
end
Base.@pure argtail(x, rest...) = rest
tail(x) = argtail(x...)
key_is(keyed::Keyed{K, V}, key::Key{K}) where {K, V} = true
key_is(any, k::Key) = false
value(k::Keyed) = k.value
Base.getproperty(t::Tuple, s::Symbol) = get_key(t, Key(s))
get_key(t::Tuple, k::Key) = get_key(tail(t), first(t), k)
get_key(t::Tuple{}, k::Key) = error("Key $k not found")
get_key(t::Tuple, any, k::Key) = get_key(t, k)
get_key(t::Tuple, keyed::Keyed{K, V}, key::Key{K}) where {K, V} = keyed.value
function test()
x = (Keyed(:a, 1), Keyed(:b, "b"))
x.a
end
@code_warntype test()
But adding getindex
methods for Tuple
is a serious type piracy, so that’s not really a workable solution. :-/
No I guess not, unless we could get this into Base
It’s also not serious type piracy because it would throw an error otherwise, that is, it wouldn’t break anyone’s code. From what I understand this type piracy is allowed in stdlibs? It might be nice to get NamedTuples into a stdlib, so that we could eventually switch it out.
EDIT: nevermind, we need them for keyword arguments.
I’ve made some progress here. I’ve rehabilitated Keys.jl. Should work with a master of Keys, TypedBools, and RecurUnroll. Fully covariant, no type piracy, fully featured replacement for NamedTuples
.