Как выполнить отправку в зависимости от типа любого из аргументов с разделителями? - PullRequest
2 голосов
/ 06 августа 2020

Рассмотрим существующую функцию в Base, которая принимает переменное количество аргументов некоторого абстрактного типа T. Я определил подтип S<:T и хотел бы написать метод, который отправляет, если какой-либо из аргументов является моим подтипом S.

В качестве примера рассмотрим функцию Base.cat, где T является an AbstractArray и S являются некоторыми MyCustomArray <: AbstractArray.

Желаемое поведение:

julia> v = [1, 2, 3];

julia> cat(v, v, v, dims=2)
3×3 Array{Int64,2}:
 1  1  1
 2  2  2
 3  3  3

julia> w = MyCustomArray([1,2,3])

julia> cat(v, v, w, dims=2)
"do something fancy"

Попытка:

function Base.cat(w::MyCustomArray, a::AbstractArray...; dims)
    pritnln("do something fancy")
end

Но это работает, только если первый аргумент - MyCustomArray.

Какой элегантный способ добиться этого?

Ответы [ 2 ]

2 голосов
/ 06 августа 2020

Я бы сказал, что невозможно сделать это чисто без пиратства типов (но, если это возможно, я также хотел бы узнать, как).

Например, рассмотрите cat, о котором вы спрашивали. Он имеет одну очень общую подпись в Base (на самом деле не требует, чтобы A был AbstractArray, когда вы пишете):

julia> methods(cat)
# 1 method for generic function "cat":
[1] cat(A...; dims) in Base at abstractarray.jl:1654

Вы можете написать специальный c метод:

Base.cat(A::AbstractArray...; dims) = ...

и проверьте, является ли какой-либо из элементов A вашим специальным массивом, но это будет пиратство типов.

Теперь проблема в том, что вы даже не можете написать Union{S, T}, так как S <: T он будет будет разрешено как просто T.

Это будет означать, что вам придется явно использовать S в подписи, но тогда даже:

f(::S, ::T) = ...
f(::T, ::S) = ...

проблематично c и компилятор попросит вас определить f(::S, ::S), поскольку приведенные выше определения приводят к неоднозначности диспетчеризации. Таким образом, даже если вы хотите ограничить количество переменных до некоторого максимального числа, вам придется аннотировать типы для всех делений A на подмножества, чтобы избежать неоднозначности отправки (что можно сделать с помощью макросов, но количество требуемых методов растет экспоненциально ).

1 голос
/ 07 августа 2020

В общем, я согласен с Богумилом, но позвольте мне сделать дополнительный комментарий. Если у вас есть контроль над тем, как вызывается cat, вы можете, по крайней мере, написать какой-нибудь код отправки признаков:

struct MyCustomArray{T, N} <: AbstractArray{T, N}
    x::Array{T, N}
end

HasCustom() = Val(false)
HasCustom(::MyCustomArray, rest...) = Val(true)
HasCustom(::AbstractArray, rest...) = HasCustom(rest...)

# `IsCustom` or something would be more elegant, but `Val` is quicker for now
Base.cat(::Val{true}, args...; dims) = println("something fancy")
Base.cat(::Val{false}, args...; dims) = cat(args...; dims=dims)

И компилятор достаточно крут, чтобы оптимизировать это:

julia> args = (v, v, w);

julia> @code_warntype cat(HasCustom(args...), args...; dims=2);
Variables
  #self#::Core.Compiler.Const(cat, false)
  #unused#::Core.Compiler.Const(Val{true}(), false)
  args::Tuple{Array{Int64,1},Array{Int64,1},MyCustomArray{Int64,1}}

Body::Nothing
1 ─ %1 = Main.println("something fancy")::Core.Compiler.Const(nothing, false)
└──      return %1

Если вы не контролируете вызовы cat, единственное средство, которое я могу придумать, чтобы заставить вышеуказанный метод работать, - это overdub методы, содержащие такой вызов , чтобы заменить соответствующие вызовы специальной реализацией. В этом случае вам даже не нужно перегружать cat, но вы можете напрямую заменить его каким-нибудь mycat, выполняющим ваши модные вещи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...