Generics 我如何用开放式类型在Julia中编写特征?

Generics 我如何用开放式类型在Julia中编写特征?,generics,julia,abstraction,traits,Generics,Julia,Abstraction,Traits,这是为了简化我所问问题的一部分: 我想写一些代码,保证在满足特定标准的类型上工作。假设今天我写了一些代码: 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)

这是为了简化我所问问题的一部分:

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

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
万岁!但是,假设这是一个深夜,我试图在第三个例子中再次这样做。我记得要执行第一步,但我忘了执行第二步

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)
现在,我为一个经理工作,如果我遇到运行时错误,他会杀了我。因此,我需要做的是编写一个特征,本质上说“如果类型实现了这个特征,那么调用
组合两个步骤是安全的。”

我想写一些像这样的东西

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然后我就知道如果
组合两个步骤
被调度,那么它将运行而不会产生错误,即这些方法不存在

同样地,
istrait(ImplementsBothSteps{X})
(为true)相当于
combine\u两个步骤
将在不存在所需方法的情况下无误运行

但是,大家都知道,我不能使用这个特质定义,因为
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\u two
,可以将任何类型作为输入),而是跨两行共享的特定类型

所以,问题是:在这种情况下你会怎么做?您希望传递一个“规范”(这里以Trait表示的契约形式),这样任何符合规范的程序员都可以保证能够使用您的函数
组合两个步骤
,但规范的定义中基本上有一个存在量词

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


顺便说一句,这听起来可能有点做作,但上面的问题和这个问题经常出现在我正在从事的一个项目中。我基本上陷入了这个问题所造成的障碍,并且有一些丑陋的解决办法,可以逐个解决问题,但没有办法解决一般问题。

概括一下我在问题中提出的使用
任何
的建议实际上也可以解决问题,尽管这很难看,也没有切中要害。假设您已经实现了一些方法

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
这也可以封装在宏中,以节省重复模式的时间,然后一旦实现了该方法,就满足了trait。这是我一直在使用的一种破解方法(而且有效)b/c它相当简单,但解决方案并不符合我问题的精神。

这是否令人满意:

@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\u types
获取方法的返回类型。诚然,这有点像黑客,但这正是我挖掘出来的。也许未来版本的
Traits.jl
会有更好的工具来实现这一点

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


当然,茱莉亚很好的内省和尝试。。。catch
允许在运行时执行所有这些检查。

使用Base.return\u类型的策略很有趣。您已经有效地编写了反射式实现“存在量词”步骤的代码。我喜欢这个。我会再考虑一下。事实上,这整个过程可以被包装成一个“元”宏,以使最终用户更简单地完成这个过程。不过,我想调查一下这个策略的性能。您是否清楚这是否会导致traits上的方法分派受到性能影响?如果编译器可以在编译时推断参数的类型,则不应该有性能影响(所涉及函数的初始生成和编译除外)。另一方面,如果类型未知,那么推断正确的函数可能会影响性能,但生成的函数是缓存的,所以希望仍然可以执行一次。这是Julia中的常见故事,当类型稳定且推断时,就会达到最佳性能(顺便说一句,
mauro3
是谁编写了
Traits.jl
)。
Traits.jl
所使用的方法在github repo的
README.md
中有详细解释(链接:),请发布详细信息。