【问题标题】:How can I write a Trait in Julia with open-ended types?如何在 Julia 中使用开放式类型编写 Trait?
【发布时间】:2016-06-08 18:07:53
【问题描述】:

这是为了简化我向here提出的问题的一部分:

我想编写一些代码,保证在满足特定条件的类型上工作。假设今天我写了一些代码:

immutable Example
    whatever::ASCIIString
end
function step_one(x::Example)
    length(x.whatever)
end
function step_two(x::Int64)
    (x * 2.5)::Float64
end
function combine_two_steps{X}(x::X)
    middle = step_one(x)
    result = step_two(middle)
    result
end
x = Example("Hi!")
combine_two_steps(x)

运行这个工程:

julia> x = Example("Hi!")
Example("Hi!")

julia> combine_two_steps(x)
7.5

然后改天我又写了一些代码:

immutable TotallyDifferentExample
    whatever::Bool
end
function step_one(x::TotallyDifferentExample)
    if x.whatever
        "Hurray"
    else
        "Boo"
    end
end
function step_two(x::ASCIIString)
    (Int64(Char(x[end])) * 1.5)::Float64
end

你知道吗,我的通用合并函数仍然有效!

julia> y = TotallyDifferentExample(false)
TotallyDifferentExample(false)

julia> combine_two_steps(y)
166.5

万岁!但是,假设现在是深夜,我正试图在第三个示例中再次执行此操作。我记得实现step_one,但是我忘记实现step_two

immutable ForgetfulExample
    whatever::Float64
end
function step_one(x::ForgetfulExample)
    x.whatever+1.0
end

现在当我运行它时,我会得到一个运行时错误!

julia> z = ForgetfulExample(1.0)
ForgetfulExample(1.0)

julia> combine_two_steps(z)
ERROR: MethodError: `step_two` has no method matching step_two(::Float64)

现在,我为一位经理工作,如果我遇到运行时错误,他会杀了我。所以为了挽救我的生命,我需要做的是编写一个 Trait,它基本上说“如果类型实现了这个 trait,那么调用 combine_two_steps 是安全的。”

我想写一些类似的东西

using Traits
@traitdef ImplementsBothSteps{X} begin
    step_one(X) -> Y
    step_two(Y) -> Float64
end
function combine_two_steps{X;ImplementsBothSteps{X}}(x::X)
    middle = step_one(x)
    result = step_two(middle)
    result
end

b/c 然后我知道 如果 combine_two_steps 曾经调度,那么它 运行而不会引发错误这些方法不存在。

等效地,istrait(ImplementsBothSteps{X})(为真)等效于 combine_two_steps 将运行而不会出现所需方法不存在错误。

但是,众所周知,我不能使用该特征定义,因为Y 没有任何意义。 (事实上​​,奇怪的是代码编译没有错误,

julia> @traitdef ImplementsBothSteps{X} begin
           step_one(X) -> Y
           step_two(Y) -> Float64
       end

julia> immutable Example
           whatever::ASCIIString
       end

julia> function step_one(x::Example)
           length(x.whatever)::Int64
       end
step_one (generic function with 1 method)

julia> function step_two(x::Int64)
           (x * 2.5)::Float64
       end
step_two (generic function with 1 method)

julia> istrait(ImplementsBothSteps{Example})
false

但类型不满足特征,即使某些 Y 存在方法。)我的第一个想法是我可以将 Y 更改为 Any 之类的东西

using Traits
@traitdef ImplementsBothSteps{X} begin
    step_one(X) -> Any
    step_two(Any) -> Float64
end

但这也失败了 b/c Any 真的应该是类似于 Some 的东西,而不是字面上的 Any 类型(因为我从未实现过可以将任何类型作为输入的方法 step_two) ,但某些特定类型在两行之间共享!

所以,问题是:在这种情况下你会怎么做?您想传递一个“规范”(此处以 Trait 表示的合同的形式),以保证任何符合规范的程序员都能够使用您的函数combine_two_steps,但规范本质上具有存在量词的定义。

有解决方法吗?编写“规范”的更好方法(例如“不要使用 Traits,使用其他东西”?)等等。

顺便说一句,这听起来可能有点做作,但上述链接的问题和这个问题在我正在进行的项目中经常出现。我基本上陷入了由这个问题引起的障碍,并且有一些丑陋的变通办法可以根据具体情况进行处理,但没有针对一般情况的方法。

【问题讨论】:

    标签: generics julia abstraction traits


    【解决方案1】:

    概括我使用Any 的问题中的建议实际上也可以工作,尽管它很丑陋并且没有真正切中要害。假设你已经实现了方法

    step_one(X) -> Y
    step_two(Y) -> Z
    

    然后你可以把特质写成

    @traitdef implements_both_steps begin
        step_one(X) -> Any
        step_two(Any) -> Z
    end
    

    只需添加一个虚拟方法

    function step_two(x::Any)
        typeof(x)==Y ? step_two(x::Y) : error("Invalid type")
    end
    

    这也可以封装在一个宏中,以节省重复模式的时间,然后一旦实现该方法,特征就会得到满足。这是我一直在使用(并且有效)b/c 的 hack,它相当简单,但解决方案不符合我的问题。

    【讨论】:

    • 能否请您发布详细信息。
    【解决方案2】:

    这样满意吗:

    @traitdef ImplementsStep2{Y} begin
        step_two(Y) -> Float64
    end
    
    # consider replacing `any` with `all`
    @traitdef AnotherImplementsBothSteps{X} begin
        step_one(X)
        @constraints begin
            any([istrait(ImplementsStep2{Y}) for Y in Base.return_types(step_one,(X,))])
        end
    end
    

    通过这些特征定义,我们有:

    julia> istrait(ImplementsStep2{Int64})
    true
    
    julia> istrait(AnotherImplementsBothSteps{Example})
    true
    

    诀窍是使用@constraints 基本上做一些非直截了当的事情。并使用Base.return_types 获取方法的返回类型。诚然,这有点像 hack,但这是我挖掘出来的。也许Traits.jl 的未来版本将为此提供更好的工具。

    我在特征定义中使用了any。这有点松懈。使用all 可能更严格但更好地表示约束,具体取决于所需的编译时检查级别。

    当然,Julia 良好的内省和try ... catch 允许在运行时进行所有这些检查。

    【讨论】:

    • 使用 Base.return_types 的策略很有趣。您已经有效地编写了反射性地实现“存在量词”步骤的代码。我喜欢这个。我会再考虑一下。事实上,这整件事可以封装到一个“元”宏中,以使最终用户的过程更简单。不过,我想调查一下这个策略的表现。您是否知道这是否会导致对特征的方法调度受到性能影响?
    • 如果编译器可以在编译时推断出参数的类型,那么应该不会影响性能(除了所涉及的函数的初始生成和编译)。另一方面,如果类型未知,则推断正确的函数可能会影响性能,但生成的函数会被缓存,因此希望这仍然会执行一次。这是 Julia 中的常见故事,当类型稳定且可推断时,会获得最佳性能(顺便说一句,mauro3 写了Traits.jl 非常赞成)。
    • Traits.jl 使用的方法在 github repo 的README.md 中有相当深入的解释(链接:github.com/mauro3/Traits.jl
    猜你喜欢
    • 2019-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多