参数类型构造函数中“where”关键字的用途

mih*_*mek 6 where-clause julia

对于 Julia 手动参数复合类型示例

struct Point{T}
    x::T
    y::T
end
Run Code Online (Sandbox Code Playgroud)

可以编写一个外部构造函数,例如

Point(x::T, y::T) where {T} = Point{T}(x, y)
Run Code Online (Sandbox Code Playgroud)

为什么where {T}需要零件,即为什么不需要

Point(x::T, y::T) = Point{T}(x, y)
Run Code Online (Sandbox Code Playgroud)

可能的?Julia 抱怨T没有定义,但它不能T::语法中找出它是一种类型吗?我是 Julia 的新手,所以我可能会遗漏一些非常基本的东西。

Bog*_*ski 7

T是一个必须定义的变量。如果你没有在whereJulia 中定义它,就会开始在外部作用域中寻找它。下面是一个例子:

julia> struct Point{T}
           x::T
           y::T
       end

julia> Point(x::T, y::T) = Point{T}(x, y)
ERROR: UndefVarError: T not defined
Stacktrace:
 [1] top-level scope at REPL[2]:1

julia> T = Integer
Integer

julia> Point(x::T, y::T) = Point{T}(x, y)
Point

julia> Point(1,1)
Point{Integer}(1, 1)
Run Code Online (Sandbox Code Playgroud)

请注意,在此示例中,您没有得到预期的结果,这可能是您所希望的Point{Int64}(1,1)

现在最棘手的部分来了:

julia> Point(1.5,1.5)
Point{Float64}(1.5, 1.5)
Run Code Online (Sandbox Code Playgroud)

你可能会问这是怎么回事。好:

julia> methods(Point)
# 2 methods for type constructor:
[1] (::Type{Point})(x::Integer, y::Integer) in Main at REPL[4]:1
[2] (::Type{Point})(x::T, y::T) where T in Main at REPL[1]:2
Run Code Online (Sandbox Code Playgroud)

关键是 The(::Type{Point})(x::T, y::T) where T已经在默认情况下被定义,当struct定义为第二个定义时,您刚刚为T = Integer.

简而言之 - 总是使用where以避免获得令人惊讶的结果。例如,如果你会写:

julia> T = String
String

julia> Point(1,1)
ERROR: MethodError: Cannot `convert` an object of type Int64 to an object of type String
Run Code Online (Sandbox Code Playgroud)

你有问题。由于 的签名在Point定义时是固定的,但在正文中T是从全局范围中获取的,并且您已将其从 更改IntegerString,因此您会收到错误消息。


扩展@Oscar Smith 指出的位置where对范围也很敏感,并告诉 Julia 在什么级别引入了“通配符”。例如:

ulia> T1 = Vector{Vector{T} where T<:Real}
Array{Array{T,1} where T<:Real,1}

julia> T2 = Vector{Vector{T}} where T<:Real
Array{Array{T,1},1} where T<:Real

julia> isconcretetype(T1)
true

julia> isconcretetype(T2)
false

julia> T1 isa UnionAll
false

julia> T2 isa UnionAll
true

julia> x = T1()
0-element Array{Array{T,1} where T<:Real,1}

julia> push!(x, [1,2])
1-element Array{Array{T,1} where T<:Real,1}:
 [1, 2]

julia> push!(x, [1.0, 2.0])
2-element Array{Array{T,1} where T<:Real,1}:
 [1, 2]
 [1.0, 2.0]
Run Code Online (Sandbox Code Playgroud)

你可以看到这T1是一个可以有实例的具体类型,我们创建了一个调用它的类型x。这种具体类型可以保存元素类型为 的向量<:Real,但它们的元素类型不必相同。

另一方面T2是一个UnionAll,即它的一些“通配符”是免费的(还不知道)。但是,限制是在匹配T2所有向量的所有具体类型中必须具有相同的元素类型,因此:

julia> Vector{Vector{Int}} <: T2
true

julia> Vector{Vector{Real}} <: T2
true
Run Code Online (Sandbox Code Playgroud)

julia> T1 <: T2
false
Run Code Online (Sandbox Code Playgroud)

换句话说,T2通配符中必须有一个具体值可以与具体类型匹配。

  • 我添加了关于“where”位置主题的评论。 (3认同)

Mat*_* B. 5

我将采取与 Bogumi? 出色答案不同的策略(100% 正确,但可能会忽略混淆的症结所在)。

这个名字没有什么特别之处T。这只是任意类型的短名称的常见约定。有点像我们经常在A矩阵或ifor 循环中使用这个名称。您碰巧在struct Point{T}和 外部构造函数中使用了相同的名称这一事实无关紧要(但对于您的代码的读者来说很方便)。你也可以做得很好:

struct Point{SpecializedType}
    x::SpecializedType
    y::SpecializedType
end
Point(x::Wildcard, y::Wildcard) where {Wildcard <: Any} = Point{Wildcard}(x, y)
Run Code Online (Sandbox Code Playgroud)

这和你写的完全一样。上述两种语法(结构体和方法)都引入了一个新名称,该名称的行为类似于专门进行适当匹配的“通配符”。当您没有where子句时,您不再引入通配符。相反,您只是引用已定义的类型。例如:

Point(x::Int, y::Int) = Point{Int}(x, y)
Run Code Online (Sandbox Code Playgroud)

也就是说,这将引用Int已经定义的 。

我想你可以说,但如果T没有定义,为什么 Julia 不能弄清楚它应该用作通配符。这可能是对的,但它在语法中引入了一点非局部性,根据定义的内容(甚至从使用的包导出),行为会截然不同。

  • 谢谢这个角度!我知道“T”只是一个任意约定,但错过了它也可以引用外部作用域中的变量。这解决了混乱: ```where {T}``` 似乎是多余的,因为它没有提供有关类型 ```T``` 的信息(从某种意义上说,它没有,例如,说 ```其中 {T&lt;:Real}```) - 但它确实在本地范围内重新定义了 ```T```。 (2认同)