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/cAny
实际上应该是类似于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
中有详细解释(链接:),请发布详细信息。