到底什么是内部构造函数?

39

简而言之:

  1. 内部构造器的准确定义是什么?在Julia-v0.6+中,可以说“任何可以使用签名typename{...}(...)(注意{}部分)调用的构造器都是内部构造器”吗?
  2. 如下面评论所述,外部唯一构造器实际上是一个显式内部构造器吗?
  3. 使用methods检查方法是否为内部/外部构造器是正确的吗?
  4. Julia自动定义的默认构造器与用户明确定义的对应构造器之间有什么区别?

顺便说一句,我知道如何使用和何时使用内部构造器。我知道什么是内部构造器,直到仅外部构造器出现并使事情变得混乱。 :(

让我们回忆一下文档中的一些声明:

1. 外部构造器方法

构造器就像Julia中的任何其他函数一样,其整体行为由其方法的组合行为定义。

2. 内部构造器方法

内部构造器方法与外部构造器方法非常相似,有两点不同:1.它在类型声明块内部声明,而不是像普通方法那样在外部声明。2.它可以访问一个特殊的本地存在的函数new,该函数创建块的类型的对象。

3. 参数化构造器

如果没有提供明确的内部构造器,则复合类型Point{T<:Real}的声明自动为每个可能的类型T<:Real提供一个内部构造器Point{T},其行为就像非参数化默认内部构造器一样。它还提供了一个单独的一般外部Point构造器,该构造器接受一对实数参数,这些参数必须是相同的类型。

我发现内部构造器方法无法直接通过methods观察,即使methods(Foo{Int})有效,它实际上也不是“像Julia中的任何其他函数一样”,常见的泛型函数不能以这种方式使用methods

julia> struct Foo{T}
    x::T
end

julia> methods(Foo)
# 2 methods for generic function "(::Type)":
(::Type{Foo})(x::T) where T in Main at REPL[1]:2  # outer ctor  「1」
(::Type{T})(arg) where T in Base at sysimg.jl:24  # default convertion method「2」

julia> @which Foo{Int}(1) # or methods(Foo{Int})
(::Type{Foo{T}})(x) where T in Main at REPL[1]:2 # inner ctor 「3」

然而,仅适用于外部的构造函数给构造函数带来了另一个问题:
julia> struct SummedArray{T<:Number,S<:Number}
           data::Vector{T}
           sum::S
           function SummedArray(a::Vector{T}) where T
               S = widen(T)
               new{T,S}(a, sum(S, a))
           end
       end
julia> methods(SummedArray)
# 2 methods for generic function "(::Type)":
(::Type{SummedArray})(a::Array{T,1}) where T in Main at REPL[1]:5 # outer ctor「4」
(::Type{T})(arg) where T in Base at sysimg.jl:24

嗯,一个类型声明块中的外部构造函数,并且它也调用了new。我想这里的目的只是为了防止Julia为我们定义默认的内部-外部构造函数对吧?但是文档中的第二条语句在这种情况下仍然成立吗?这对新用户来说很困惑。
这里,我读到还有一种形式的内部构造函数:
julia> struct Foo{T}
     x::T
     (::Type{Foo{T}})(x::T) = new{T}(x) 
   end

julia> methods(Foo)
# 1 method for generic function "(::Type)":
(::Type{T})(arg) where T in Base at sysimg.jl:24

julia> methods(Foo{Int})
# 2 methods for generic function "(::Type)":
(::Type{Foo{T}})(x::T) where T in Main at REPL[2]:35」
(::Type{T})(arg) where T in Base at sysimg.jl:24

这与规范形式Foo{T}(x::T) where {T} = new(x)相差甚远,但结果似乎是相同的。

因此,我的问题是内部构造函数的准确定义是什么?在Julia-v0.6+中,是否可以说“任何使用签名typename{...}(...)(注意{}部分)调用的构造函数都是内部构造函数”?


2
我的看法是,当您想要绕过默认的外部构造函数(无论是隐式还是显式的)以执行初始化/测试等操作时,可以使用内部构造函数。当存在内部构造函数时,默认的外部构造函数不再适用,除非您明确定义一个。我不同意您在上面一些示例中所称的内部/外部构造函数(但这可能只是一个笔误)。另请参见此问题(免责声明:我的),作为内部构造函数适用的示例。 - Tasos Papastylianou
2
我认为这里内部和外部的区别会让问题变得更加混乱。问题更多地涉及默认、显式、隐式、专用和通用的区别。文档所说的是,当没有提供显式内部构造函数时,存在默认构造函数,其等效于某些显式内部构造函数公式。因此,我将[1]称为通用默认构造函数,[3]称为专用默认构造函数,[4]是显式内部构造函数(也恰好是参数化的),[5]也是如此(尽管写法稍微有些复杂)。 - Tasos Papastylianou
2
@TasosPapastylianou 是的,自动内部和外部构造函数的机制很重要(感谢您的澄清),但实际上,我可以看到它在未来会发生变化。内部构造函数分配和生成一致的结构体,外部构造函数包装这些结构体以提供各种创建方法的概念是定义的核心。从这个意义上讲,我甚至可以看到定义new调用内部构造函数在结构体定义之外的能力。也许甚至可以覆盖某些特定类型参数的内部构造函数以添加额外的约束条件,这可能会很方便。 - Dan Getz
2
@Gnimuc 我同意,这可能需要更清晰的表述。标题应该更关注可用的隐式默认内部(和外部)参数构造函数的范围,以及如果定义了显式内部构造函数,则这些构造函数不再适用的情况。如果是这种情况,那么您只能依赖于创建适当的外部构造函数,这些构造函数实际上是显式内部构造函数的包装器,并且您不能再依赖于未被激活的隐式构造函数,这是自然而然的结果。 - Tasos Papastylianou
2
谢谢你的回复!我刚刚在这里提交了一个问题 https://github.com/JuliaLang/julia/issues/23022,让我们在那里继续讨论。 - Gnimuc
显示剩余14条评论
1个回答

24

举个例子,假设你想定义一个类型来表示偶数:

julia> struct Even
          e::Int
       end

julia> Even(2)
Even(2)

目前为止还不错,但您还希望构造函数拒绝奇数,而目前的Even(x)没有实现这一点:

julia> Even(3)
Even(3)

所以你试图编写自己的构造函数,如下:

julia> Even(x) = iseven(x) ? Even(x) : throw(ArgumentError("x=$x is odd"))
Even

并且……请敲鼓,它不起作用:

julia> Even(3)
Even(3)

为什么?让我们问一下Julia她刚才叫了什么:

julia> @which Even(3)
Even(e::Int64) in Main at REPL[1]:2

这不是您定义的方法(请查看参数名称和类型),而是隐式提供的构造函数。也许我们应该重新定义一下?嗯,不要自己尝试:

julia> Even(e::Int) = iseven(e) ? Even(e) : throw(ArgumentError("e=$e is odd"))
Even
julia> Even(2)
ERROR: StackOverflowError:
Stacktrace:
 [1] Even(::Int64) at ./REPL[11]:0
 [2] Even(::Int64) at ./REPL[11]:1 (repeats 65497 times) 

我们刚刚创建了一个无限循环:我们重新定义了Even(e)让它递归调用自身。现在我们面临一个鸡生蛋的问题:我们想重新定义隐式构造函数,但在定义的函数中需要调用一些其他的构造函数。正如我们所看到的,调用Even(e)并不是可行的选项。

解决方法是定义一个内部构造函数:

julia> struct Even
          e::Int
          Even(e::Int) = iseven(e) ? new(e) : throw(ArgumentError("e=$e is odd"))
       end


julia> Even(2)
Even(2)

julia> Even(3)
ERROR: ArgumentError: e=3 is odd
..

在内部构造函数中,您可以使用 new() 语法调用原始的隐式构造函数。这种语法对于外部构造函数不可用。如果尝试使用它,将会得到一个错误:

julia> Even() = new(2)
Even

julia> Even()
ERROR: UndefVarError: new not defined 
..

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接