Как избежать нестабильности типов при нарезке кортежа - PullRequest
4 голосов
/ 01 мая 2020

Этот вопрос является адаптированной версией того, что здесь в справочной службе JuliaLang Zulip.


Предположим, у меня есть функция, которая принимает неоднородно типизированный Tuple и возвращает срез этого кортежа, где индексы срезов могут быть статически выведены только из информации о типе. Как я могу написать свою функцию таким образом, чтобы тип вывода был правильно выведен?

Например, предположим, что моя функция равна

function f(t::Tuple, A::Array{T, N}) where {T, N}
    if T <: AbstractFloat
        imin = 1
    elseif T <: Integer
        imin = 2
    else
        imin = 3
    end
    imax = N+2    
    t[imin:imax]
end 

. Мы видим, что вывод типа только показывает, что это дает Tuple, а не его длину или типы элементов, даже если вся необходимая информация доступно во время компиляции?

julia> let t = (:a, "b", 2, 3.0, Val(1), 2+im), A = rand(Int, 3,3)
           Base.return_types(f, Tuple{typeof(t), typeof(A)})
       end
1-element Array{Any,1}:
 Tuple

Как мне написать f так, чтобы это работало?

Ответы [ 2 ]

5 голосов
/ 01 мая 2020

Стратегия, с которой мне было бы удобнее (но, может быть, есть более простой способ?), Состоит в том, чтобы написать функцию @generated, чтобы вручную убедиться, что julia выполняет операции уровня типа, которые я хочу во время компиляции:

@generated function f2(t::Tuple, A::Array{T, N}) where {T, N}
    if T <: AbstractFloat
        imin = 1
    elseif T <: Integer
        imin = 2
    else
        imin = 3
    end
    imax = N+2
    out_expr = Expr(:tuple, (:(t[$i]) for i ∈ imin:imax)...)
end 

Идея заключается в том, что в сгенерированном теле функции во время компиляции мы определяем, что такое imin и imax, а затем вручную строим выражение для нашего тела функции, которое читает (t[imin], t[imin+1], ..., t[imax-1], t[imax]).

По любым причинам, Джулия может лучше рассуждать о последовательности getindex(::Tuple, ::Int), чем о нарезке кортежа, даже со статически известным слайсом, поэтому, вручную создавая это выражение, компилятор может сделайте то, что мы хотим:

julia> let t = (:a, "b", 2, 3.0, Val(1), 2+im), A = rand(Int, 3,3)
           Base.return_types(f2, Tuple{typeof(t), typeof(A)})
       end
1-element Array{Any,1}:
 Tuple{String,Int64,Float64}

Вуаля, выводимый тип вывода - это Tuple длины 3, чьи элементы статически известны как String, Int и Float64!

1 голос
/ 03 мая 2020

Вы должны вызывать некоторые неэкспортированные функции, но вы можете сделать это без функции @generated, например, такой:

julia> function f(t::Tuple, A::Array{T, N}) where {T, N}
           t = Base.IteratorsMD.split(t, Val(N+2))[1]
           if !(T<:AbstractFloat)
               t = Base.tail(t)
               if !(T<:Integer)
                   t = Base.tail(t)
               end
           end
           return t
       end
f (generic function with 1 method)

julia> let t = (:a, "b", 2, 3.0, Val(1), 2+im), A = rand(Int, 3,3)
           Base.return_types(f, Tuple{typeof(t), typeof(A)})
       end
1-element Array{Any,1}:
 Tuple{String,Int64,Float64}

Есть преимущества в этом при непродолжительных манипуляциях с кортежами: ваш код, вероятно, скомпилируется быстрее, а также Revise -able.

...