-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
constructors and functions behave differently with respect to isbitstype
, sizeof
, etc.
#38716
Comments
I think it's already explained in that zulipchat that Type{T} isn't a singleton, and closure Functions don't work differently from this |
#36591 helped address some of the confusion, and even though not all
Can you explain why that is a justification for the current behavior? For closures julia> function foo(x)
g() = x
end
foo (generic function with 1 method)
julia> typeof(foo(0))
var"#g#1"{Int64}
julia> T = typeof(foo(1))
var"#g#1"{Int64}
julia> isa(foo(0), T)
true
julia> isa(foo(1), T)
true
julia> foo(0) === foo(1)
false
In what sense is julia> Core.Compiler.issingletontype(Type{Int64})
false
julia> Base.issingletontype(Type{Int64})
false is it not a singleton? Are there arguments that satisfy the following function: function test(a, b)
a !== b && isa(a, Type{Int64}) && isa(b, Type{Int64})
end |
Int64 is the type of a closure, because it holds data. In that same sense, Type{Int64} is also, as it captures T from the scope (values can be captured in multiple ways). Yes, in the type domain, those are inhabited by |
It is still not clear how to reconcile:
Lines 1165 to 1170 in 65382c7
I understand that
Sorry, I can't figure out what "those" refers to. More concretely, I hope this chunk helps me communicate what I want, and why I think it's reasonable, and helps others communicate why not. I compare
to try to discover what's the rub. function foo(x)
g(y) = x + y
end
struct MyInt64
_::Int64
end
things = [MyInt64, foo(0), identity]
println("\n", "all can be called:")
@show things[1](789)
@show things[2](789)
@show things[3](789)
println("\n", "1 and 2 hold data")
@show sizeof(things[1])
@show sizeof(things[2])
@show sizeof(things[3])
println("\n", "1, 2, 3 are all different types:")
@show typeof(things[1])
@show typeof(things[2])
@show typeof(things[3])
println("\n", "1 is unique in relation to `Type{thing}`")
for x in [:(things[1]), :(things[2]), :(things[3])]
eval(:(@show isa($x, typeof($x))))
eval(:(@show isa($x, Type{$x})))
end
# Define `my_typeof` that also satisfies `isa(x, my_typeof(x))`
my_typeof(T) = typeof(T)
my_typeof(T::DataType) = Type{T}
println("\n", "Double-check `my_typeof`")
for x in [:(things[1]), :(things[2]), :(things[3])]
eval(:(@show isa($x, my_typeof($x))))
end
println("\n", "typeof singleton status:")
@show Base.issingletontype(typeof(things[1]))
@show Base.issingletontype(typeof(things[2]))
@show Base.issingletontype(typeof(things[3]))
println("\n", "my_typeof singleton status (no change):")
@show Base.issingletontype(my_typeof(things[1]))
@show Base.issingletontype(my_typeof(things[2]))
@show Base.issingletontype(my_typeof(things[3]))
struct Empty{T}
end
make_Empty_direct(T) = Empty{T}()
make_Empty_my(x) = make_Empty_direct(my_typeof(x))
my_issingletontype(x) = Base.issingletontype(x)
my_issingletontype(::Type{Type{T}}) where {T} = Base.isconcretetype(T)
println("\n", "intuitive predicate for singleton. 1 and 3 are the same")
for x in [:(things[1]), :(things[2]), :(things[3])]
eval(:(@show my_issingletontype(my_typeof($x))))
end
# if there is a way to get from `typeof(identity)` to `identity`, that would allow a better definition for `get_thing`.
println("\n", "Define get_thing")
for thing in things
!my_issingletontype(my_typeof(thing)) && continue
T = Empty{my_typeof(thing)}
println("\ton $T")
@eval get_thing(::$(T), args...) = $(thing)
end
call_thing(e::Empty, args...) = get_thing(e)
println("\n", "1 and 3 are the same, and 1 and 2 are different.")
@show call_thing(make_Empty_my(things[1]), 789)
# @show call_thing(make_Empty_my(things[2]), 789) # this cannot mean anything.
@show call_thing(make_Empty_my(things[3]), 789)
# Since that feat is possible, then this certainly is:
struct Wrap{T}
_::T
end
make_Wrap_my(x) = Wrap{my_typeof(x)}(x)
get_callable(w::Wrap) = w._
get_callable(w::Wrap) = w._
call_thing(thing::Wrap, args...) = get_callable(thing)(args...)
println("\n", "this works for all three:")
@show call_thing.(make_Wrap_my.(things), 789)
println("\n", "and so in principle `sizeof(make_Wrap_my(Int64))` can be zero, but:")
@show sizeof.(make_Wrap_my.(things))
nothing output all can be called:
(things[1])(789) = MyInt64(789)
(things[2])(789) = 789
(things[3])(789) = 789
1 and 2 hold data
sizeof(things[1]) = 8
sizeof(things[2]) = 8
sizeof(things[3]) = 0
1, 2, 3 are all different types:
typeof(things[1]) = DataType
typeof(things[2]) = var"#g#13"{Int64}
typeof(things[3]) = typeof(identity)
1 is unique in relation to `Type{thing}`
things[1] isa typeof(things[1]) = true
things[1] isa Type{things[1]} = true
things[2] isa typeof(things[2]) = true
things[2] isa Type{things[2]} = false
things[3] isa typeof(things[3]) = true
things[3] isa Type{things[3]} = false
Double-check `my_typeof`
things[1] isa my_typeof(things[1]) = true
things[2] isa my_typeof(things[2]) = true
things[3] isa my_typeof(things[3]) = true
typeof singleton status:
Base.issingletontype(typeof(things[1])) = false
Base.issingletontype(typeof(things[2])) = false
Base.issingletontype(typeof(things[3])) = true
my_typeof singleton status (no change):
Base.issingletontype(my_typeof(things[1])) = false
Base.issingletontype(my_typeof(things[2])) = false
Base.issingletontype(my_typeof(things[3])) = true
intuitive predicate for singleton. 1 and 3 are the same
my_issingletontype(my_typeof(things[1])) = true
my_issingletontype(my_typeof(things[2])) = false
my_issingletontype(my_typeof(things[3])) = true
Define get_thing
on Empty{Type{MyInt64}}
on Empty{typeof(identity)}
1 and 3 are the same, and 1 and 2 are different.
call_thing(make_Empty_my(things[1]), 789) = MyInt64
call_thing(make_Empty_my(things[3]), 789) = identity
this works for all three:
call_thing.(make_Wrap_my.(things), 789) = Any[MyInt64(789), 789, 789]
and so in principle `sizeof(make_Wrap_my(Int64))` can be zero, but:
sizeof.(make_Wrap_my.(things)) = [8, 8, 0] Also to emphasize the point julia> reinterpret(Int64, [things[2]])
1-element reinterpret(Int64, ::Array{var"#g#13"{Int64},1}):
0 and the others do not. |
And as a better concrete example of how this behavior affects the memory allocation of broadcasting machinery right now: using BenchmarkTools
function foo(fs, n)
x = 0
for i = 1:n
bc = Base.broadcasted(fs[mod1(i, end)], 1:i)
x += sum(bc)
end
return x
end julia> @benchmark foo([x->x, identity], 1000)
BenchmarkTools.Trial:
memory estimate: 124.42 KiB
allocs estimate: 4958
--------------
minimum time: 718.429 μs (0.00% GC)
median time: 751.449 μs (0.00% GC)
mean time: 793.813 μs (0.32% GC)
maximum time: 2.212 ms (61.57% GC)
--------------
samples: 6293
evals/sample: 1
julia> @benchmark foo([x->x, Int], 1000)
BenchmarkTools.Trial:
memory estimate: 140.08 KiB
allocs estimate: 5459
--------------
minimum time: 703.243 μs (0.00% GC)
median time: 785.698 μs (0.00% GC)
mean time: 800.921 μs (0.41% GC)
maximum time: 2.364 ms (52.07% GC)
--------------
samples: 6236
evals/sample: 1 which essentially boils down to julia> sizeof(Broadcast.broadcasted(identity, 1:10))
16
julia> sizeof(Broadcast.broadcasted(Int, 1:10))
24 |
Okay, but that seems to show that it's faster (though likely it's about the same speed), so it doesn't show why to change this. There is an optimization predicate (Core.Compiler.hasuniquerep) that already does classify these in some places. |
I should not have included the times---the focus is on the memory estimate. And really what I care about is the storage as shown by the The bottom line is that in I hope that helps clarify why this is an issue for me. @mbauman helped me see that I wasn't clear on this, and I intend to write a summary of the conversation we had on slack, and that may help clarify further. |
For one use case, I am thinking of
Int64
andround
as functions:and I want to represent their types respectively. Unfortunately
And more pertinent to my problem:
If I understand correctly, if
T
inFoo{T}
is a singleton type, that is there is only one valuex::T
, thensizeof(Foo{T})
can in principle be0
. There is only onex::typeof(round)
and only onex::Type{Int64}
.(reference: https://julialang.zulipchat.com/#narrow/stream/225542-helpdesk/topic/size.20of.20tuple.20.2F.20struct.20of.20singleton.20type)
The text was updated successfully, but these errors were encountered: