实例化类型时的关键字参数

Keyword argument when instantiating a Type

假设我有以下类型:

type Foo
    a::Int64
    b::Int64
end

我可以用

实例化它
bar = Foo(1,2)

有没有办法在这里使用关键字,因为在上面我必须记住a是第一个,b是第二个。像这样:

bar = Foo(a=1, b=2)

编辑:

如果从以下函数调用, 的解决方案将不起作用:

#!/usr/bin/env julia

type Foo
    a::Float64
    b::Float64
end

function main()
    Foo(;a=1, b=2.0) = Foo(a,b)
    bar = Foo(a=1, b=2.0)
    println(bar.a)
end

main()

为什么?有解决方法吗?

编辑 2:

在函数内部不起作用:

#!/usr/bin/env julia

type Foo
    a::Int64
    b::Int64
end

function main()
    Foo(;a=1, b=2) = Foo(a,b)
    bar = Foo(a=1, b=2)
    println(bar.a)
end

main()

但如果将其从函数中取出——它会起作用:

#!/usr/bin/env julia

type Foo
    a::Int64
    b::Int64
end

# function main()
    Foo(;a=1, b=2) = Foo(a,b)
    bar = Foo(a=1, b=2)
    println(bar.a)
# end

# main()

是的,但是您需要参数的默认值:

julia> type Foo
           a::Int64
           b::Int64
       end

julia> Foo(;a=1, b=2) = Foo(a, b)
Foo

julia> Foo(b=10)
Foo(1,10)

julia> Foo(a=40)
Foo(40,2)

julia> Foo(a=100, b=200)
Foo(100,200)

编辑

让我们分解语法Foo(;a=1, b=1) = Foo(a, b)

首先,定义一个与类型同名的函数会为该类型定义一个新的构造函数。这意味着我们正在定义另一个函数,它将创建 Foo 类型的对象。手册中有一个关于构造函数的 whole chapter,所以如果您不熟悉该术语,您应该阅读它们。

其次,Julia 区分位置参数和关键字参数。位置参数是 Julia 中的默认参数。对于位置参数,名称会根据参数定义的顺序分配给函数参数,然后传递给函数。例如,如果我定义一个函数 f(a, b) = .... 我知道我传递给 f 的第一个参数在函数体内将被称为 a (无论函数的名称是什么变量在调用范围内)。

关键字参数在 Julia 中的处理方式不同。在调用函数时,使用语法 argument=value 为函数的关键字参数提供非默认值。在 Julia 中,您通过使用分号 (;) 将某些参数与标准位置参数分开并赋予它们默认值来告诉编译器某些参数将成为关键字参数。例如,如果我们定义 g(a; b=4) = ...,我们可以通过将它作为传递给 g 的第一件事来给 a 一个值,而通过说 b=something 来给 b 一个值。如果我们想用参数 a=4b=5 调用 g 函数,我们会写 g(4; b=5) (注意这里的 ; 可以用 ,,但我发现如果我改用 ;,它可以帮助我记住 b 是关键字参数)。

有了这个,我们终于可以理解上面的语法了:

Foo(;a=1, b=2) = Foo(a, b)

这将创建一个具有零位置参数和两个关键字参数的新构造函数:ab,其中 a 的默认值为 1b 默认为 2。该函数声明的右侧仅采用 ab 并将它们传递给默认的内部构造函数(当我们声明类型时自动为我们定义) Foo.


编辑 2

我发现了您在函数内定义新的外部构造函数时遇到的问题。

function main()
    Foo(;a=1, b=2.0) = Foo(a,b)

实际上创建了一个全新的函数 Foo,它是 main 函数的本地函数。因此,左侧创建了一个新的本地 Foo,右侧尝试调用该新的本地 Foo。问题是没有为接受两个位置 Int64 参数的本地 Foo 定义方法。

如果你真的想这样做你需要告诉 main 函数通过指定 Foo 属于全局范围来向 Foo 外部函数添加一个方法.这有效:

function main()
    global Foo
    Foo(;a=1, b=2.0) = Foo(a,b)
    bar = Foo(a=1, b=2.0)
    println(bar.a)
end

关于使用内部构造函数。当然你可以这样做,但你还需要定义一个默认的内部构造函数。这是因为如果您不定义任何新的内部构造函数,Julia 会为您生成一个默认构造函数。如果您确实决定创建自己的一个,那么如果您想要拥有它,则必须手动创建默认构造函数。这样做的语法是

type Foo
    a::Int64
    b::Int64

    # Default constructor
    Foo(a::Int64, b::Int64) = new(a, b)

    # our new keyword constructor
    Foo(;a::Int64=1, b::Int64=2) = new (a, b)
end

我应该注意,对于这个特定的用例,您几乎肯定不想将关键字 version 定义为内部构造函数,而是像我在回答开头所做的那样定义为外部构造函数。 Julia 中的惯例是尽可能使用最少数量的内部构造函数——仅在需要确保字段之间的不变关系或部分初始化对象的情况下使用它们。