Should passing Val(dims) to cat be better documented?

Would documenting the reason for passing Val(dims) to to the dims keyword of cat lead to more purrfect documentation than what we have meow?

Currently the documentation for cat indicates that one can pass a Val to dims, but it does not explain why.

An experienced Julia user might know this is due to type stability. Should the documentation include a note about that or is this obvious?

julia> @code_warntype cat([5], [2], dims = 2)
MethodInstance for Core.kwcall(::NamedTuple{(:dims,), Tuple{Int64}}, ::typeof(cat), ::Vector{Int64}, ::Vector{Int64})
  from kwcall(::Any, ::typeof(cat), A...) @ Base abstractarray.jl:1981
Arguments
  _::Core.Const(Core.kwcall)
  @_2::NamedTuple{(:dims,), Tuple{Int64}}
  @_3::Core.Const(cat)                                              A::Tuple{Vector{Int64}, Vector{Int64}}
Locals
  @_5::Int64
  dims::Int64
Body::Any                                                         1 ─       nothing
β”‚         Core.NewvarNode(:(@_5))
β”‚   %3  = Core.isdefined(@_2, :dims)::Core.Const(true)
└──       goto #3 if not %3
2 ─       (@_5 = Core.getfield(@_2, :dims))
└──       goto #4
3 ─       Core.Const(:(Core.UndefKeywordError(:dims)))
└──       Core.Const(:(@_5 = Core.throw(%7)))
4 β”„ %9  = @_5::Int64
β”‚         (dims = %9)
β”‚   %11 = (:dims,)::Core.Const((:dims,))
β”‚   %12 = Core.apply_type(Core.NamedTuple, %11)::Core.Const(NamedTuple{(:dims,)})
β”‚   %13 = Base.structdiff(@_2, %12)::Core.Const(NamedTuple())
β”‚   %14 = Base.pairs(%13)::Core.Const(Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}}())
β”‚   %15 = Base.isempty(%14)::Core.Const(true)
└──       goto #6 if not %15
5 ─       goto #7
6 ─       Core.Const(:(Core.tuple(@_2, @_3)))
└──       Core.Const(:(Core._apply_iterate(Base.iterate, Base.kwerr, %18, A)))
7 β”„ %20 = Base.:(var"#cat#153")::Core.Const(Base.var"#cat#153")
β”‚   %21 = Core.tuple(dims, @_3)::Tuple{Int64, typeof(cat)}
β”‚   %22 = Core._apply_iterate(Base.iterate, %20, %21, A)::Any
└──       return %22


julia> @code_warntype cat([5], [2], dims = Val(2))
MethodInstance for Core.kwcall(::NamedTuple{(:dims,), Tuple{Val{2}}}, ::typeof(cat), ::Vector{Int64}, ::Vector{Int64})
  from kwcall(::Any, ::typeof(cat), A...) @ Base abstractarray.jl:1981
Arguments                                                           _::Core.Const(Core.kwcall)                                        @_2::Core.Const((dims = Val{2}(),))                               @_3::Core.Const(cat)                                              A::Tuple{Vector{Int64}, Vector{Int64}}                          Locals
  @_5::Val{2}
  dims::Val{2}
Body::Matrix{Int64}
1 ─       nothing
β”‚         Core.NewvarNode(:(@_5))
β”‚   %3  = Core.isdefined(@_2, :dims)::Core.Const(true)
└──       goto #3 if not %3                                       2 ─       (@_5 = Core.getfield(@_2, :dims))
└──       goto #4                                                 3 ─       Core.Const(:(Core.UndefKeywordError(:dims)))
└──       Core.Const(:(@_5 = Core.throw(%7)))
4 β”„ %9  = @_5::Core.Const(Val{2}())
β”‚         (dims = %9)
β”‚   %11 = (:dims,)::Core.Const((:dims,))
β”‚   %12 = Core.apply_type(Core.NamedTuple, %11)::Core.Const(NamedTuple{(:dims,)})
β”‚   %13 = Base.structdiff(@_2, %12)::Core.Const(NamedTuple())     β”‚   %14 = Base.pairs(%13)::Core.Const(Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}}())
β”‚   %15 = Base.isempty(%14)::Core.Const(true)
└──       goto #6 if not %15
5 ─       goto #7
6 ─       Core.Const(:(Core.tuple(@_2, @_3)))
└──       Core.Const(:(Core._apply_iterate(Base.iterate, Base.kwerr, %18, A)))
7 β”„ %20 = Base.:(var"#cat#153")::Core.Const(Base.var"#cat#153")
β”‚   %21 = Core.tuple(dims, @_3)::Core.Const((Val{2}(), cat))
β”‚   %22 = Core._apply_iterate(Base.iterate, %20, %21, A)::Matrix{Int64}
└──       return %22
1 Like

Do we need to document it at all now that Julia v1.10+ does the constant propagation as one would prefur?

julia> f(x, y) = cat(x, y; dims = 2)
f (generic function with 1 method)

julia> @code_warntype f([5],[2])
MethodInstance for f(::Vector{Int64}, ::Vector{Int64})
  from f(x, y) @ Main REPL[1]:1
Arguments
  #self#::Core.Const(f)
  x::Vector{Int64}
  y::Vector{Int64}
Body::Matrix{Int64}
1 ─ %1 = (:dims,)::Core.Const((:dims,))
β”‚   %2 = Core.apply_type(Core.NamedTuple, %1)::Core.Const(NamedTuple{(:dims,)})
β”‚   %3 = Core.tuple(2)::Core.Const((2,))
β”‚   %4 = (%2)(%3)::Core.Const((dims = 2,))
β”‚   %5 = Core.kwcall(%4, Main.cat, x, y)::Matrix{Int64}
└──      return %5

I see Val as an ugly band-aid of Julia’s past. Now that the compiler is much better about constant propagation it’s largely not needed.

3 Likes