Not sure if this is a bug, but here is a MWE:
Keys(::Type{NamedTuple{names, data}}) where {names, data} = names
@generated function foo(x)
Core.println(Keys(x))
nothing
end
foo(NamedTuple{(:a, :b)})
#+RESULTS:
MethodError: no method matching Keys(::Type{Type{NamedTuple{(:a, :b)}}})
Anyone know what might be happening here? Should I open an issue?
I find it very bizarre that Type{Type{NamedTuple{(:a, :b)}}}
got constructed, that seems like a bug.
versioninfo()
#+RESULTS:
: Julia Version 1.7.1
: Commit ac5cc99908* (2021-12-22 19:35 UTC)
: Platform Info:
: OS: Linux (x86_64-pc-linux-gnu)
: CPU: AMD Ryzen 5 2600 Six-Core Processor
: WORD_SIZE: 64
: LIBM: libopenlibm
: LLVM: libLLVM-12.0.1 (ORCJIT, znver1)
: Environment:
: JULIA_NUM_THREADS = 6
That seems like the correct behavior, right?
If you call foo(a)
, then "inside" the generated function, x = typeof(a)
, right?
You called foo(a)
where a = NamedTuple
, and thus typeof(a) = Type{NamedTuple}
.
So inside the generated function, we have x = typeof(a) = Type{NamedTuple}
, right?
So, now, inside the generated function, you call Keys(x)
.
If x = Type{NamedTuple}
, and you call Keys(x)
, then what should the type signature of that Keys
method be?
It should be Keys(::Type{Type{NamedTuple}}) = ...
, right?
I guess I've just never experienced that before when passing types to generated functions. I thought that Type
just existed so that we didn't get DataType
, so I thought we didn't double wrap things in Type
Well, we need to be able to distinguish between e.g. foo(NamedTuple())
and foo(NamedTuple)
, right?
That's not the same thing
It'd be confusing if a @generated
function changes its behavior when the argument is a type or not. So, I'd say the current behavior of @generated
is correct and "ergonomic."
We generally don't want to distinguish between Type{T}
and T
when T
is a type.
If you know the argument is a type, you can use @generated foo(::T) where {T} ...
. Then T
is the same for generator code and the runtime code.
Huh. Today I learned that Core.Typeof(Type{T})
gives Type{Type{T}}
. For some reason I thought I had remembered that we didn't do this
Takafumi Arakaki (tkf) said:
If you know the argument is a type, you can use
@generated foo(::T) where {T} ...
. ThenT
is the same for generator code and the runtime code.
Yeah, that's what I normally do, but the use case where I ran into this, I had a variable number of heterogeneous arguments so that wasn't an option.
It is important to note that T
and Type{T}
are different.
julia> 1 isa Int
true
julia> 1 isa Type{Int}
false
Type{T}
is a type of T
julia> Int isa Type{Int}
true
julia> Int isa DataType
true
julia> Type{Int} <: DataType
true
Yeah, makes sense, thanks for the clarification
Type{T}
is useful because it is a singleton representation of "type of T
"
I guess I'll need to splat the arguments, then have an inner method that I pass the arguments to as a tuple.
Ahh... yes, I remember that vararg is a bit weird inside @generated
. I'd probably define a function to unwrap the Type
julia> @generated f(xs...) = (global XS = xs; nothing);
julia> f(Int, Nothing)
julia> XS
(Type{Int64}, Type{Nothing})
julia> instanceof(::Type{Type{T}}) where {T} = T;
julia> map(instanceof, XS)
(Int64, Nothing)
Yeah, good idea. I'll just build that into Keys
.
I was fiddling again today with something @Brian Chen asked about yesterday on Slack, and the part I left as an 'exercise to the reader' was a bit more frustrating than I had anticipated, so here it is in case you want it Brian. A vararg complement to Base.structdiff
that isn't slow at runtime:
Keys(::Type{<:NamedTuple{names}}) where {names} = names
Keys(::Type{Type{T}}) where {T} = Keys(T)
@generated function structintersect(a::NamedTuple{an}, rest::Union{NamedTuple, Type{<:NamedTuple}}...) where {an}
names = Tuple(intersect(an, Keys.(rest)...))
data = Expr(:tuple, (:(getproperty(a, $(QuoteNode(name)))) for name in names)...)
:(NamedTuple{$names}($data))
end
structintersect((;a=1, b=2, c=3, d=4), (;b=1, c=3, d=4), NamedTuple{(:b, :d)})
#+RESULTS:
: (b = 2, d = 4)
Mason Protter has marked this topic as resolved.
Think I should bother trying to open a PR to Base with this? I suspect Base.structdiff
only exists because they have a need to use it internally
It would have been nice if we had a uniform dict/set API so that we could motivate this a bit better as a prerequisite of intersect
-on-NamedTuple
Not that I'm against it or anything. I just don't know if it's OK or not
That’s my feeling also.
Last updated: Dec 28 2024 at 04:38 UTC