This is either an API request or a documentation request. It'd be nice to have an API that does
f(::Val{x}) where x = x
If it already exists, it'd be nice to mention it in Val
's docstring.
If it does not exist, maybe getindex
or get
would be a nice function for this? Or maybe add a new function?
This came up in https://github.com/JuliaArrays/StaticArrays.jl/pull/702#discussion_r379254870
This makes sense to me.
Any suggestions for the name? unwrap
?
I think getindex
doesn鈥檛 really make sense because you can鈥檛 pass an index.
get
would make sense.
My thought process was that Val
is _somewhat_ like Ref
as they are both "something that wraps a single value":
julia> x = Ref(1);
julia> x[]
1
julia> y = Val(1);
julia> y[] # doesn't work now, but maybe it's OK if it does?
Ohhh I see what you mean.
Now that I鈥檝e seen it written out, I actually kind of like that API.
Nice, thanks for making this issue! Out of those options I think I'd prefer getindex
to get
. The analogy to Ref
seems reasonable and I can't think of any negative consequences right now.
One thing to think about is whether it would be useful to allow unwrapping just the type of a value type, e.g. Val{1}
, as well. Unfortunately, we can't use getindex
here, as it already creates an empty array. If Val(1)[]
works, users might then also intuitively think that Val{1}[]
works the same way, and be really confused when it creates an empty array, so I don't know whether getindex
is the right syntax for this.
One thing to think about is whether it would be useful to allow unwrapping just the type of a value type, e.g.
Val{1}
, as well. Unfortunately, we can't usegetindex
here, as it already creates an empty array. IfVal(1)[]
works, users might then also intuitively think thatVal{1}[]
works the same way, and be really confused when it creates an empty array, so I don't know whethergetindex
is the right syntax for this.
That鈥檚 a good point. Maybe a new function unwrap
is best. It would accept both types and instances.
Perhaps the right function is only
?
One thing to think about is whether it would be useful to allow unwrapping just the type of a value type, e.g.
Val{1}
, as well. Unfortunately, we can't usegetindex
here, as it already creates an empty array. IfVal(1)[]
works, users might then also intuitively think thatVal{1}[]
works the same way, and be really confused when it creates an empty array, so I don't know whethergetindex
is the right syntax for this.
Would you expect Val{1}[]
to create an array? Tbh I didn't. And I neither see a usecase for it as those types are singletons. So we might simply overload getindex for Val types to return the argument.
Base.getindex(::Type{Val{x}}) where x = x
EDIT: There are nevertheless good reasons against this, keep reading.
So we might simply overload getindex for Val types to return the argument.
No this is bad for predictability and general consistency. getindex
on types is already a concession to the "a fonction has one meaning" rule. If there are exception to this concession, things get harder to understand. The more exceptions, the more difficult the language is to learn.
Ah, main problem being
function fuse(x::T, n::Int) where T
collected = T[]
for i=1:n
push!(collected, x+i)
end
return collected
end
style functions. Where arbitrary stuff could happen.
I just wanted this yesterday.
I like unwrap
, because it would also be good to have:
t = Tuple{Int, String}
julia> unwrap(t)
(Int, String)
etc. I also find using getindex
on types confusing.
As a point of reference, ResultTypes
uses unwrap
to get a value out of a Result
. Conceptually, it's quite similar (although Result
s may also contain errors (Exceptions
) instead.
(Adding this to Base
would also require ResultTypes
to import it and would break anything using ResultTypes.jl
until that happens...)
If thinking of Val(3)
as a container like Ref(3)
, then the tuple idea sounds more like eltype(Ref(3)) == Int
than like Ref(3)[] == only(Ref(3)) == 3
. Perhaps there should be a function like eltypes(Tuple{Int, String}) == (Int, String)
, but making eltype(Val(3))
return 3
would be weird.
If thinking of Val(3) as a container like Ref(3)
I'm not sure this is a good comparison. Val
is a type that uses no memory, Ref
is reference to a value in memory, so It's more like (1, 2)
than Tuple{1, 2}
. eltype
is for the type of something that is stored in memory somewhere. To me your Tuple
and Val
examples are equally weird.
The reason I like unwrap
for Tuple
is that you can use the Tuple
type separately from any actual realised ()
tuple - say to hold an arbitrary list of Symbols that you can dispatch on like Tuple{:x,:y}
. So it's kind of like Val
. But handling various lengths of Tuple{:a,:b,:c,:d...}
is annoying, so unwrap
would be useful.
OK, if you are using T = Tuple{:a,:b,:c,:d}
to store things then I understand why you'd want this. Calling T.parameters
is close, but perhaps parameters(T)
is too generic a name to export? type_parameters
?
Of course the storage behind x=Ref(1)
and y=Val(2)
are different, but the x[], y[]
analogy is to view them both as sort-of containers, even if this precise syntax has issues.
Seeing unwrap
on Tuple has an unexplained downvote, it's actually immediately useful:
https://github.com/JuliaDiffEq/LabelledArrays.jl/blob/master/src/lsliced.jl#L32
https://github.com/cesaraustralia/DynamicGrids.jl/blob/master/src/mapinteractions.jl#L73
It would replace these functions in published packages (although addmitedly both written by me ;).
One thing to think about is whether it would be useful to allow unwrapping just the type of a value type, e.g.
Val{1}
, as well.
Do we need this? It's probably easy to tell from the context that you have Val
type or value. So, if you have a Val{c}
type T
, you can just do T()[]
.
Perhaps the right function is
only
?
I think it's better to define only
as a corollary, not as a primary API. To get only
, I think you need to satisfy the iterator interface. Also, I'd suggest to start from a minimal API. I don't think we need to add a full iterator API to Val
for now.
unwrap
on Tuple
I can see that it's useful. StaticArrays.jl also does this. But I think it's better to have a separate API. Importantly, this API needs to return a tuple (c,)
from Val(c)
and it requires another syntax to "unwrap" it.
One thing to think about is whether it would be useful to allow unwrapping just the type of a value type, e.g.
Val{1}
, as well
I'm not sure we need this either: I think it's best to avoid passing Val
around as a type and use values instead. Using values for traits leads to more natural dispatch (no need for ::Type{Val{X}}
everywhere; less need for complicated UnionAlls). You'll note that all the traits in Base have transitioned to using instances rather than types and Val is no exception. In general I feel like it's best to just avoid type-domain computation if you can.
One thing to think about is whether it would be useful to allow unwrapping just the type of a value type, e.g. Val{1}, as well
The meaning of this interface method is quite different depending on wether it just works on the instance or also on the type, and is probably the reason for the wide range of suggestions in this thread. I was _mostly_ thinking about using it on the type! the obvious case being in generated functions and macros, but it can be useful elsewhere as well.
The meaning of this interface method is quite different depending on wether it just works on the instance or also on the type, and is probably the reason for the wide range of suggestions in this thread.
Ah, yes, this explains a lot. Nice observation!
So, how about focusing on the API only on values? As a I said, concrete Val
type is always constructable so it's easy to use API on values even if you just have types. Also, I think it would be cleaner to have a generic API for type parameters and use it against Val
just as you'd use it for Tuple
etc., if you don't want to construct it.
Then the next question may be that "isn't v[]
redundant if we can do the_api(typeof(v))
?" But I think v[]
(or get(v)
or whatever(v)
) still makes sense as one of the advantage of Val
is to avoid type-level computation and the code look like run-time computation like any other Julia code.
Perhaps the right function is
only
?[...] I don't think we need to add a full iterator API to
Val
for now.
But I can see the syntax v[]
may be asking for v
to be an iterator (ignoring Int[1, 2, 3]
for now). But then adding iterator interface makes it incompatible with the existing broadcasting behavior:
julia> tuple.(Val(1), 1:2)
2-element Array{Tuple{Val{1},Int64},1}:
(Val{1}(), 1)
(Val{1}(), 2)
This behavior is quite explicitly defined by this:
But, since AbstractString
has incompatible broadcasting and iteration behavior, maybe this is OK. I also note that non-iterator types like IO
defines getindex
/get
as a part of IOContext
interface.
So, I still think getindex
is a decent API to have.
Most helpful comment
One thing to think about is whether it would be useful to allow unwrapping just the type of a value type, e.g.
Val{1}
, as well. Unfortunately, we can't usegetindex
here, as it already creates an empty array. IfVal(1)[]
works, users might then also intuitively think thatVal{1}[]
works the same way, and be really confused when it creates an empty array, so I don't know whethergetindex
is the right syntax for this.