Julia 朱莉娅:带内部构造函数的参数类型:new和typeof

Julia 朱莉娅:带内部构造函数的参数类型:new和typeof,julia,Julia,试图了解参数类型和内部方法可用的新函数。该手册声明“创建该类型新对象的内部构造函数可用的特殊函数”。请参阅手册中关于新建的章节和手册中关于内部构造函数方法的章节 考虑设计用于计算x之和的内部方法,其中x可以是向量或元组,并且被赋予参数类型T。自然需要的是x元素的类型由它们的总和s继承。我似乎不需要新的,对吗 struct M{T} x::T s function M(x) s = sum(x) x,s end end julia

试图了解参数类型和内部方法可用的
函数。该手册声明“创建该类型新对象的内部构造函数可用的特殊函数”。请参阅手册中关于新建的章节和手册中关于内部构造函数方法的章节

考虑设计用于计算
x
之和的内部方法,其中
x
可以是向量或元组,并且被赋予参数类型
T
。自然需要的是
x
元素的类型由它们的总和
s
继承。我似乎不需要新的,对吗

struct M{T}
    x::T
    s
    function M(x)
        s = sum(x)
        x,s
    end
end


julia> M([1,2,3])
([1, 2, 3], 6)

julia> M([1.,2.,3.])
([1.0, 2.0, 3.0], 6.0)

julia> typeof(M([1.,2.,3.]))
Tuple{Vector{Float64}, Float64}
编辑:更正我打算让内部构造函数的最后一行是
M(x,s)
。。。这仍然是一个有趣的问题,所以我不会纠正它。
M(x,s)
新的{typeof(x)}(x,s)
有何不同

我见过的
new
的一个用法是与
typeof()
结合使用,类似于:

struct M{T}
    x::T
    s
    function M(x)
        s = sum(x)
        new{typeof(x)}(x,s)
    end
end

julia> M([1,2,3])
M{Vector{Int64}}([1, 2, 3], 6)

julia> M([1.,2.,3.])
M{Vector{Float64}}([1.0, 2.0, 3.0], 6.0)
如果要将
s
约束为与
x
相同的类型,该怎么办?也就是说,例如,如果
x
是一个向量,那么
s
应该是一个向量(在这种情况下,是一个元素的向量)。我该怎么做?如果我用
x,new{typeof(x)}(s)
替换内部构造函数的最后一行,我会得到可以理解的错误:

MethodError: Cannot `convert` an object of type Int64 to an object of type Vector{Int64}

记住,构造函数是用来构造某些东西的。具体而言,构造函数
M
设计用于构造
M
类型的值。您的示例构造函数

struct M{T}
    x::T
    s
    function M(x)
        s = sum(x)
        x,s
    end
end
表示对表达式
M([1 2 3])
求值的结果是一个元组,而不是
M
的实例。如果我在野外遇到这样一个构造函数,我会假设它是一个bug并报告它
new
是一种内部魔法,允许您实际构造
M
类型的值

这是一个抽象的问题。如果您首先想要一个元组,那么就不要考虑名为
M
的结构,只需在模块范围内定义一个返回元组的函数
M
。但是,如果您打算将其视为一种特殊的数据类型,可能用于动态分派,但甚至只是出于自我文档目的,那么构造函数应该返回类型为
M

的值以下是规则:

  • 如果您正在为类型
    M
    编写外部构造函数,则构造函数应通过最终调用内部构造函数返回
    M
    的实例,如下所示:
    M()

  • 如果您正在编写内部构造函数,这将覆盖默认的内部构造函数。因此,必须通过调用
    new()
    返回
    M
    的实例

  • 新的
    “特殊函数”允许构造一个还没有构造函数的类型。观察以下示例:

    julia> struct A
               x::Int
               function A(x)
                   A(x)
               end
           end
    
    julia> A(4)
    ERROR: StackOverflowError:
    Stacktrace:
     [1] A(::Int64) at ./REPL[3]:4 (repeats 79984 times)
    
    这是
    a
    构造函数的循环定义,它会导致堆栈溢出。您无法通过引导来提升自己,因此Julia提供了
    new
    函数来规避此问题

    您应该为
    new
    函数提供与结构中字段数量相等的参数。请注意,
    new
    函数将尝试转换其输入的类型,以匹配结构中声明的字段类型:

    julia> struct B
               x::Float64
               B(x) = new(x)
           end
    
    julia> B(5)
    B(5.0)
    
    julia> B('a')
    B(97.0)
    
    julia> B("a")
    ERROR: MethodError: Cannot `convert` an object of type String to an object
    of type Float64
    
    (上面
    B
    的内部构造函数与默认内部构造函数完全相同。)

    定义参数化类型时,必须为
    new
    函数提供与类型参数数量相等的参数数量(且顺序相同),类似于参数化类型的默认内部构造函数。首先观察如何使用参数化类型的默认内部构造函数:

    julia> struct Foo{T}
               x::T
           end
    
    julia> Foo{String}("a")
    Foo{String}("a")
    
    现在,如果您正在为
    Foo
    编写一个内部构造函数,而不是在构造函数内部编写
    Foo{T}(x)
    ,您将用
    new
    替换
    Foo
    ,如下所示:
    new{T}(x)

    您可能需要
    typeof
    来帮助定义构造函数,但通常不需要。以下是定义
    M
    类型的一种方法:

    struct M{I, T}
        x::I
        s::T
    
        function M(x::I) where I
            s = sum(x)
            new{I, typeof(s)}(x, s)
        end
    end
    
    我在这里使用的是
    typeof
    ,因此
    I
    可以是任何返回数字的iterable类型:

    julia> typeof(M(1:3))
    M{UnitRange{Int64},Int64}
    
    julia> g = (rand() for _ in 1:10)
    Base.Generator{UnitRange{Int64},var"#5#6"}(var"#5#6"(), 1:10)
    
    julia> typeof(M(g))
    M{Base.Generator{UnitRange{Int64},var"#5#6"},Float64}
    
    请注意,在参数化类型的内部构造函数中使用
    new
    时,需要为类型提供参数:

    julia> struct C{T}
               x::Int
               C(x) = new(x)
           end
    ERROR: syntax: too few type parameters specified in "new{...}" around REPL[6]:1
    

    谢谢西尔维奥。我意识到我打算在内部构造函数中使用
    M(x,s)
    !然而,你的答案非常有用,所以我不打算编辑这个问题。如果您能评论一下返回
    M(x,s)
    new{something}(x,s)
    之间的区别,我将不胜感激。谢谢我相信,在你的情况下,没有太大的区别。如果您正在覆盖
    M(x,s)
    (在这种情况下,您不能调用原始
    M(x,s)
    ,因为您刚刚替换了它)。您可以将每个内部构造函数视为隐式调用
    new
    ,除非您重写它以执行不同的操作。在您的例子中,您的
    M(x)
    调用了由Julia运行时生成的
    M(x,s)
    ,并隐式调用了
    new(x,s)
    。非常感谢您提供了这个超级详细的答案。这对我帮助很大!就在上面写着“我在这里使用
    typeof
    ,因此
    I
    可以是任何返回数字的可编辑类型,
    typeof(s)
    可以等价地替换为
    T
    ?如果我想要
    x::Float64
    ?我将
    {I,T}
    更改为
    {T}
    ,抑制
    中的I
    并写入
    新的{Float64,typeof(s)}(x,s)
    ,但这是一个