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()
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)
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
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With