Julia 我如何分配与两种类型相关的特征,其中共同满足该特征的第二种类型由第一种唯一确定?

Julia 我如何分配与两种类型相关的特征,其中共同满足该特征的第二种类型由第一种唯一确定?,julia,traits,Julia,Traits,假设我有一个Julia特征,它与两种类型相关:一种类型是一种可能满足某种部分特征的“基本”类型,另一种是由基本类型唯一确定的关联类型。(也就是说,BaseType->AssociatedType之间的关系是一个函数。)总之,这些类型满足我感兴趣的复合特性 例如: using Traits @traitdef IsProduct{X} begin isnew(X) -> Bool coolness(X) -> Float64 end @traitdef IsProd

假设我有一个Julia特征,它与两种类型相关:一种类型是一种可能满足某种部分特征的“基本”类型,另一种是由基本类型唯一确定的关联类型。(也就是说,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
现在如果我定义

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)
将始终吐出
数组{AssociatedType}
,并且

do_something_that_makes_tuples(x::BaseType)
将始终吐出
元组{Int64,BaseType,AssociatedType}

并且,一个这样的关系适用于所有对
;例如,如果
BatmanType
RobinType
关联的基本类型,
SupermanType
LexLutherType
始终关联的基本类型,则

do_something_that_makes_tuple(x::BatManType)
将始终输出
元组{Int64,BatmanType,RobinType}
,和

do_something_that_makes_tuple(x::SuperManType)
将始终输出
元组{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的函数输出有关,我认为这也在考虑之中,尽管我还无法从这个问题的角度准确地解释这个问题。

    Whoa-Whoa-Whoa-Whoa。看起来你正在尝试重新实现所有的推断。jl.我不建议这样做t、 您真正想要完成什么?您的示例函数只需理解一下即可编写:
    get_measurements(similous_items)=[measurements(item)for item in similous_items]
    。狭义地说,我希望能够添加类型断言
    ::Array{M}
    在该表达式之后,假设
    ::X
    在方法签名中,并使该方法可调用,并进行编译,即使其知道
    M
    是什么。但更一般地说,我希望能够使用符号
    M
    执行任意操作,因为满足两个条件(a)符号
    X
    已被赋予t的含义
    @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