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

时间:2016-02-25 19:13:56

标签: generics julia abstraction traits

这是为了简化我问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)

现在,我为一位经理工作,如果我遇到运行时错误,他将会杀了我。所以我需要做的就是拯救我的生命就是写一个基本上说'#34;如果类型实现这个特性的特性,那么调用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方法可以采用任何类型作为输入),但是在两条线上共享的某些特定类型!

所以,问题是:在这种情况下你会做什么?你想传递一个" spec" (这里是Trait表达的合同形式),这样任何符合规范的程序员都可以保证能够使用你的函数combine_two_steps,但规范在其定义中基本上有一个存在量词。 / p>

有解决方法吗?编写"规范"的更好方法(例如"不要使用特征,使用别的东西"?)等等。

顺便说一句,这可能听起来很人为,但上面提到的问题和这个问题在我正在研究的项目中经常出现。我基本上陷入了由此问题引起的障碍,并且具有逐案处理的难看的解决方法,但没有解决一般情况。

2 个答案:

答案 0 :(得分: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它相当简单,但解决方案不符合我的问题。

答案 1 :(得分:1)

这是否令人满意:

@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来获取方法的返回类型。不可否认,这有点像黑客,但这就是我的挖掘所提出的。或许Traits.jl的未来版本可能会有更好的工具。

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

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