Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keyword argument when instantiating a Type

Tags:

julia

Suppose I have the following type:

type Foo
    a::Int64
    b::Int64
end

I can instantiate this with

bar = Foo(1,2)

Is there a way to use keywords here, because in the above I have to remember that a is first, and b is second. Something like this:

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

Edit:

The solution by spencerlyon2 doesn't work if called from the function:

#!/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()

Why? Is there a workaround?

Edit 2:

Doesn't work from inside a function:

#!/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()

but if take it out of the function -- it works:

#!/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()
like image 494
Adobe Avatar asked Mar 25 '15 16:03

Adobe


1 Answers

Yep, but you will need default values for the arguments:

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)

Edit

Let's break down the syntax Foo(;a=1, b=1) = Foo(a, b).

First, defining a function with the same name as a type defines a new constructor for that type. This means we are defining another function that will create objects of type Foo. There is a whole chapter on constructors in the manual, so if that term is unfamiliar to you you should read up on them.

Second, Julia distinguishes between positional and keyword arguments. Positional arguments are the default in Julia. With positional arguments names are assigned to function arguments based on the order in which the arguments were defined and then passed into the function. For example if I define a function f(a, b) = .... I know that the first argument I pass to f will be referred to as a within the body of the function (no matter what the name of the variable is in the calling scope).

Keyword arguments are treated differently in Julia. You give a function's keyword arguments non-default values using the syntax argument=value when calling the function. In Julia you tell the compiler that certain arguments are to be keyword arguments by separating them from the standard positional arguments with a semicolon (;) and giving them default values. For example, if we define g(a; b=4) = ... we can give a a value by making it the first thing passed to g and b a value by saying b=something. If we wanted to call the g function with arguments a=4, b=5 we would write g(4; b=5) (note the ; here can be replaced by a ,, but I have found it helps me remember that b is a keyword argument if I use a ; instead).

With that out of the way, we can finally understand the syntax above:

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

This creates a new constructor with zero positional arguments and two keyword arguments: a and b, where a is given a default value of 1 and b defaults to 2. The right hand side of that function declaration simply takes the a and the b and passes them in order to the default inner constructor (that was defined automatically for us when we declared the type) Foo.


EDIT 2

I figured out the problem you were having when defining a new outer constructor inside a function.

The lines

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

actually create a completely new function Foo that is local to the main function. So, the left hand side creates a new local Foo and the the right hand side tries to call that new local Foo. The problem is that there is not a method defined for the local Foo that takes two positional Int64 arguments.

If you really want to do this you need to tell the main function to add a method to the Foo outer function, by specifying that Foo belongs to the global scope. This works:

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

About using inner constructors. Sure you can do this, but you will also want to define a default inner constructor. This is because if you do not define any new inner constructors, Julia generates a default one for you. If you do decide to create one of your own, then you must create the default constructor by hand if you want to have it. The syntax for doing this is

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

I should note that for this particular use case you almost certainly do not want to define the keyword version as an inner constructor, but rather as an outer constructor like I did at the beginning of my answer. It is convention in Julia to use the minimum number of inner constructors as possible -- using them only in cases where you need to ensure invariant relationships between fields or partially initialize an object.

like image 97
spencerlyon2 Avatar answered Sep 29 '22 06:09

spencerlyon2