Как я могу отсылать черты, относящиеся к двум типам, где второй тип, который удовлетворяет признаку, однозначно определяется первым?

Скажем, у меня есть черта Джулии, относящаяся к двум типам: один тип - своего рода "базовый" тип, который может удовлетворять своего рода частичной чертой, а другой - связанный тип, который однозначно определяется базовым типом. (То есть отношение из BaseType → AssociatedType является функцией.) Вместе эти типы удовлетворяют сложному признаку, который представляет интерес для меня.

Например:

using Traits

@traitdef IsProduct{X} begin
    isnew(X) -> Bool
    coolness(X) -> Float64
end

@traitdef IsProductWithMeasurement{X,M} begin
    @constraints begin
        istrait(IsProduct{X})
    end
    measurements(X) -> M
    #Maybe some other stuff that dispatches on (X,M), e.g.
    #fits_in(X,M) -> Bool
    #how_many_fit_in(X,M) -> Int64
    #But I don't want to implement these now
end

Теперь вот несколько типов примеров. Пожалуйста, игнорируйте данные примеров; они просто обозначаются как MWE, и в деталях нет ничего важного:

type Rope
    color::ASCIIString
    age_in_years::Float64
    strength::Float64
    length::Float64
end

type Paper
    color::ASCIIString
    age_in_years::Int64
    content::ASCIIString
    width::Float64
    height::Float64
end

function isnew(x::Rope) 
    (x.age_in_years < 10.0)::Bool 
end
function coolness(x::Rope) 
    if x.color=="Orange" 
        return 2.0::Float64
    elseif x.color!="Taupe" 
        return 1.0::Float64
    else 
        return 0.0::Float64
    end
end
function isnew(x::Paper) 
    (x.age_in_years < 1.0)::Bool 
end
function coolness(x::Paper) 
    (x.content=="StackOverflow Answers" ? 1000.0 : 0.0)::Float64 
end

Поскольку я определил эти функции, я могу сделать

@assert istrait(IsProduct{Rope})
@assert istrait(IsProduct{Paper})

И теперь, если я определяю

function measurements(x::Rope)
    (x.length)::Float64
end

function measurements(x::Paper)
    (x.height,x.width)::Tuple{Float64,Float64}
end

Тогда я могу сделать

@assert istrait(IsProductWithMeasurement{Rope,Float64})
@assert istrait(IsProductWithMeasurement{Paper,Tuple{Float64,Float64}})

До сих пор так хорошо; они работают без ошибок. Теперь я хочу написать такую ​​функцию, как:

@traitfn function get_measurements{X,M;IsProductWithMeasurement{X,M}}(similar_items::Array{X,1})
    all_measurements = Array{M,1}(length(similar_items))
    for i in eachindex(similar_items)
        all_measurements[i] = measurements(similar_items[i])::M
    end
    all_measurements::Array{M,1}
end

В общем, эта функция предназначена для примера "Я хочу использовать тот факт, что я, как программист, знаю, что BaseType всегда связан с AssociatedType, чтобы помочь компилятору с типом вывода. что всякий раз, когда я выполняю определенную задачу [в этом случае get_measurements, но в общем случае это может работать в нескольких случаях], тогда я хочу, чтобы компилятор выводил тип вывода этой функции последовательно узорным образом".

То есть, например,

do_something_that_makes_arrays_of_assoc_type(x::BaseType)

всегда выплевывает Array{AssociatedType} и

do_something_that_makes_tuples(x::BaseType)

всегда выплевывает Tuple{Int64,BaseType,AssociatedType}.

AND, одно из таких соотношений выполняется для всех пар <BaseType,AssociatedType>; например если BatmanType является базовым типом, к которому привязан RobinType, а SupermanType является базовым типом, к которому всегда привязано LexLutherType, тогда

do_something_that_makes_tuple(x::BatManType)

всегда будет выводить Tuple{Int64,BatmanType,RobinType}, а

do_something_that_makes_tuple(x::SuperManType)

всегда будет выводить Tuple{Int64,SupermanType,LexLutherType}.

Итак, я понимаю это отношение, и я хочу, чтобы компилятор понимал это ради скорости.

Теперь вернемся к примеру функции. Если это имеет смысл, вы поймете, что хотя определение функции, приведенное в качестве примера, является "правильным" в том смысле, что оно удовлетворяет этой взаимосвязи и компилируется, оно не может быть вызвано, поскольку компилятор не понимает отношения между X и M, хотя я и делаю. В частности, поскольку M не отображается в сигнатуре метода, Джулия не может отправить функцию.

До сих пор единственное, что я решил сделать для решения этой проблемы, - это создать своеобразное обходное решение, где я "вычисляю" связанный тип "на лету", и я все еще могу использовать отправку методов для выполнения этого вычисления. Рассмотрим:

function get_measurement_type_of_product(x::Rope)
    Float64
end
function get_measurement_type_of_product(x::Paper)
    Tuple{Float64,Float64}
end
@traitfn function get_measurements{X;IsProduct{X}}(similar_items::Array{X,1})
    M = get_measurement_type_of_product(similar_items[1]::X)
    all_measurements = Array{M,1}(length(similar_items))
    for i in eachindex(similar_items)
        all_measurements[i] = measurements(similar_items[i])::M
    end
    all_measurements::Array{M,1}
end

Тогда действительно это компилируется и может быть вызвано:

julia> get_measurements(Array{Rope,1}([Rope("blue",1.0,1.0,1.0),Rope("red",2.0,2.0,2.0)]))
2-element Array{Float64,1}:
 1.0
 2.0

Но это не идеально, потому что (а) я должен каждый раз переопределять эту карту, хотя мне кажется, что я уже говорил компилятору о связи между X и M, заставляя их удовлетворять черту, и (б) насколько я могу предположить - возможно, это неправильно; У меня нет прямых доказательств для этого - компилятор не обязательно сможет оптимизировать, а также хочу, поскольку связь между X и M "скрыта" внутри возвращаемого значения вызова функции.

Одна последняя мысль: если бы у меня была такая способность, то я бы идеально сделал что-то вроде этого:

@traitdef IsProduct{X} begin
    isnew(X) -> Bool
    coolness(X) -> Float64
    ∃ ! M s.t. measurements(X) -> M
end

а затем иметь некоторый способ обращения к типу, который однозначно свидетельствует о существовании, так, например,

@traitfn function get_measurements{X;IsProduct{X},IsWitnessType{IsProduct{X},M}}(similar_items::Array{X,1})
    all_measurements = Array{M,1}(length(similar_items))
    for i in eachindex(similar_items)
        all_measurements[i] = measurements(similar_items[i])::M
    end
    all_measurements::Array{M,1}
end

потому что это было бы как-то отправлено.

Итак: каков мой конкретный вопрос? Я спрашиваю, учитывая, что вы, по-видимому, понимаете, что мои цели -

  • Как мой код демонстрирует такую ​​структуру в целом, так что Я могу эффективно повторить этот шаблон дизайна во многих случаях а затем программировать в абстрактном виде на высоком уровне X и M, и
  • do (1) таким образом, чтобы компилятор все еще мог оптимизировать в меру своих возможностей/знал о связи между типы как I, кодер, am

то как мне это сделать? Я думаю, что ответ

  • Используйте Traits.jl
  • Сделайте что-то очень похожее на то, что вы сделали до сих пор
  • Также сделайте ____ очень умную вещь ____, чтобы ответчик указал,

но я открыт для идеи, что на самом деле правильный ответ

  • Отбросьте этот подход, вы думаете о проблеме не так.
  • Вместо этого подумайте об этом так: ____ MWE ____

Я также был бы вполне удовлетворен ответами формы

  • То, что вы просите, - это "сложная" функция Julia, которая все еще находится в разработке, и ожидается, что она будет включена в v0.x.y, поэтому просто подождите...

и я с меньшим энтузиазмом (но все еще любопытно услышать), например,

  • Отказаться от Джулии; вместо этого используйте язык ________, который предназначен для такого типа вещей.

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

Ответы