Как я могу отсылать черты, относящиеся к двум типам, где второй тип, который удовлетворяет признаку, однозначно определяется первым?
Скажем, у меня есть черта Джулии, относящаяся к двум типам: один тип - своего рода "базовый" тип, который может удовлетворять своего рода частичной чертой, а другой - связанный тип, который однозначно определяется базовым типом. (То есть отношение из 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, который, как я ее воспринимаю, также рассматривается, хотя я не смог разобраться в точном представлении этой проблемы с точки зрения этого.