例如:
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}
。
而且,对于所有的<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
未出现在方法签名中,因此Julia无法对该函数进行分派。
到目前为止,我想到的唯一解决此问题的方法是创建一种解决方法,其中我可以“计算”相关类型,并且仍然可以使用方法分派来执行此计算。考虑:
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
但这并不理想,因为 (a) 我每次都需要重新定义这个映射表,即使我感觉我已经通过使它们满足特征向编译器描述了
X
和 M
之间的关系,而且 (b) 据我所猜测——也许这是错误的;我没有直接证据——编译器可能不能像我想的那样进行优化,因为 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
的抽象方式进行编程,并且 - 以这种方式执行 (1),使编译器仍然能够尽其所能地进行优化 / 与类型之间的关系就像我一样,也意识到它们之间的关系。
那么,我该怎么做呢? 我认为答案是:
- 使用
Traits.jl
- 做与你迄今所做的相似的事情
- 还要进行一些巧妙的操作,由回答者指示
但我也接受这个想法:实际上,正确的答案是:
- 放弃这种方法,你正在错误地思考问题
- 相反,这样考虑:MWE
我也会对以下形式的回答感到完全满意:
- 你所要求的是 Julia 的“高级”功能,目前仍在开发中,预计将包含在 v0.x.y 中,所以请耐心等待...
对于以下回答,我不太热衷(但仍然很好奇):
- 放弃 Julia;相反,使用专为此类事情设计的语言 ________
我还认为这可能与定义 Julia 函数输出类型的问题有关,尽管我还没有能够解决这个问题的确切表示。
get_measurements(similar_items) = [measurements(item) for item in similar_items]
。 - mbaumanBase.return_types
)。请参阅https://dev59.com/opTfa4cB1Zd3GeqPRHmw。 - Dan Getz