我试图理解在 Julia 中的输入并遇到以下问题Array。我写了一个函数bloch_vector_2d(Array{Complex,2});详细的实施是无关紧要的。打电话时,这里是投诉:
julia> bloch_vector_2d(rhoA)
ERROR: MethodError: no method matching bloch_vector_2d(::Array{Complex{Float64},2})
Closest candidates are:
bloch_vector_2d(::Array{Complex,2}) at REPL[56]:2
bloch_vector_2d(::StateAB) at REPL[54]:1
Stacktrace:
[1] top-level scope at REPL[64]:1
Run Code Online (Sandbox Code Playgroud)
问题是父类型的数组不会自动成为子类型数组的父级。
julia> Complex{Float64} <: Complex
true
julia> Array{Complex{Float64},2} <: Array{Complex,2}
false
Run Code Online (Sandbox Code Playgroud)
我认为将这一点强加给 julia 是有道理的Array{Complex{Float64},2} <: Array{Complex,2}。或者在 Julia 中实现这一点的正确方法是什么?任何帮助或意见表示赞赏!
此处的 Julia 手册中详细讨论了此问题。
引用其中的相关部分:
换句话说,用类型论的说法,Julia 的类型参数是不变的,而不是协变的(甚至是逆变的)。这是出于实际原因:虽然
Point{Float64}从概念上讲,任何实例都可能类似于实例,但这Point{Real}两种类型在内存中具有不同的表示形式:
- 的实例
Point{Float64}可以简洁有效地表示为一对立即数 64 位值;- 的实例
Point{Real}必须能够保存任何一对 的实例Real。由于作为 Real 实例的对象可以具有任意大小和结构,因此在实践中, 的实例Point{Real}必须表示为一对指向单独分配的 Real 对象的指针。
现在回到你的问题如何编写方法签名,那么你有:
julia> Array{Complex{Float64},2} <: Array{<:Complex,2}
true
Run Code Online (Sandbox Code Playgroud)
注意区别:
Array{<:Complex,2}表示其 eltype 是 的子类型的所有二维数组类型的联合Complex(即没有数组将具有此确切类型)。Array{Complex,2}是一种数组可以具有的类型,这种类型意味着您可以Complex在其中存储可以具有混合参数的值。下面是一个例子:
julia> x = Complex[im 1im;
1.0im Float16(1)im]
2×2 Array{Complex,2}:
im 0+1im
0.0+1.0im 0.0+1.0im
julia> typeof.(x)
2×2 Array{DataType,2}:
Complex{Bool} Complex{Int64}
Complex{Float64} Complex{Float16}
Run Code Online (Sandbox Code Playgroud)
另请注意,符号Array{<:Complex,2}与书写相同Array{T,2} where T<:Complex(或更紧凑Matrix{T} where T<:Complex)。
这更多是评论,但我可以毫不犹豫地发布它。这个问题很常见。我会告诉你为什么会出现这种现象。
ABag{Apple}是一个Bag{Fruit},对吧?因为,当我有 a 时JuicePress{Fruit},我可以给它 aBag{Apple}来制作一些果汁,因为Apples 是Fruits。
但是现在我们遇到了一个问题:我的果汁厂,我在其中加工不同的水果,失败了。我订购了一个新的JuicePress{Fruit}. 现在,不幸的是我得到了一个替代品JuicePress{Lemon}——但Lemons 是Fruits,所以 a 肯定JuicePress{Lemon}是 a JuicePress{Fruit},对吗?
然而,第二天,我将苹果喂给新印刷机,机器爆炸了。我希望你明白为什么:JuicePress{Lemon}是不是一个JuicePress{Fruit}。恰恰相反:aJuicePress{Fruit}是 a JuicePress{Lemon}-- 我可以用与水果无关的压榨机压榨柠檬!JuicePress{Plant}不过,他们本可以寄给我的,因为Fruits 是Plants。
现在我们可以变得更抽象了。真正的原因是:函数输入参数是逆变的,而函数输出参数是协变的(在理想化设置中)2。也就是说,当我们有
f : A -> B
Run Code Online (Sandbox Code Playgroud)
然后我可以传入 的超类型A,并最终得到 的子类型B。因此,当我们修正第一个参数时,诱导函数
(Tree -> Apple) <: (Tree -> Fruit)
Run Code Online (Sandbox Code Playgroud)
每当Apple <: Fruit- 这是协变情况,它会保留 的方向<:。但是当我们修复第二个时
(Fruit -> Juice) <: (Apple -> Juice)
Run Code Online (Sandbox Code Playgroud)
每当Fruit >: Apple- 这颠倒了 的方向<:,因此被称为相反的变体。
这会延续到其他参数数据类型,因为在那里,您通常也有“类似输出”的参数(如Bag)和“类似输入”的参数(如JuicePress)。也可能有参数的行为既不像(例如,当它们以两种方式出现时)——这些被称为invariant。
现在有两种方法可以用参数类型的语言解决这个问题。在我看来,更优雅的方法是标记每个参数:没有注释意味着不变,+意味着协变,-意味着逆变(这有技术原因——据说这些参数出现在“正”和“负位置”)。所以我们有Bag[+T <: Fruit], 或JuicePress[-T <: Fruit]( 应该是 Scala 语法,但我还没有尝试过)。不过,这使得子类型化更加复杂。
要走的另一条路线是 Julia 所做的(以及顺便说一句,Java):所有类型都是不变的1,但您可以在调用站点指定上下联合。所以你必须说
makejuice(::JoicePress{>:T}, ::Bag{<:T}) where {T}
Run Code Online (Sandbox Code Playgroud)
这就是我们得出其他答案的方式。
1元组除外,但这很奇怪。
2这个术语来自范畴论。-Hom函子在第一个参数中是逆变的,在第二个参数中是协变的。通过“健忘”函子从类别Typ到关系Typ下的 es的偏序集,可以直观地实现子类型化<:。而 CT 术语反过来又来自张量。
| 归档时间: |
|
| 查看次数: |
175 次 |
| 最近记录: |