generics - 如何使用开放式类型在 Julia 中编写 Trait?

标签 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)

现在,我为一位经理工作,如果我遇到运行时错误,他会杀了我。所以我需要做的来挽救我的生命是写一个 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 ,但规范在其定义中本质上有一个存在量词。

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

顺便说一句,这听起来可能是人为的,但是上面链接的问题和这个问题在我正在从事的项目中经常出现。我基本上陷入了由这个问题引起的障碍,并且有逐案工作的丑陋解决方法,但没有解决一般情况的方法。

最佳答案

概括我在使用 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,它相当简单,但解决方案不符合我的问题的精神。

关于generics - 如何使用开放式类型在 Julia 中编写 Trait?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35636248/

相关文章:

Java arraylist 找不到构造函数,使用 arrays.aslist

generics - "Missing parameter type"在采用函数参数的重载泛型方法中

generics - Scala:抽象类型与泛型

julia - 为什么数组+=(没有@.)会产生如此多的内存分配?

julia - 如何在 Julia 中获取数组的最小值?

java - Animate ball JPanel subclass is not abstract错误

java - 抽象数据与抽象行为

Java:编写一个抛出任何 RuntimeException 的通用方法

multidimensional-array - Julia 中的维度不匹配

c# - 如何以可靠的方式管理外部资源?