KeyedTuple and inference

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.