Just adding information for my own ignorance. Of course, when I thought to define the variable without defining its values, I was willing to define the values afterwards using
x = test()
x.a = 1
in which case, I find now, I need to define a “mutable struct” instead:
mutable struct test
a :: Int64
test() = new()
# test() = new(4) # could be used to start with a default value
test(a) = new(a)
end
you can also take a look at the Paramters package (https://github.com/mauro3/Parameters.jl) which allows to define default values for immutable structs via the @with_kw macro.
Adding some more information. I am not a huge fan of saving keystrokes :-). The redefinition of the “<” operator can be done using the following, which allows for any complexity of the definition of the operator:
function Base.:<( x::test, y::test )
if x.a < y.a
return true
else
return false
end
end
Additionally, I found out that any function can be used in this way (this is pretty cool):
function in( x::Int64, y::test )
if x == y.a || x == y.b
return true
end
return false
end
struct test
a :: Int64
b :: Int64
end
x = test(1,3)
julia> 2 in x
false
julia> 3 in x
true
EDIT: In consideration of the comment below, the “in” function can be written in concise notation using:
I didn’t say it is better, it is just another way to write the same thing.
For this specific example of course the concise notation is very nice. But I was thinking on the possibility where the result of the “x < y” involves a complex code, in which case knowing that the same thing can be written as any standard function is good, I think.
For example, it could be that evaluating “x < y” involved solving an optimization problem.
I much prefer prefixing with the module when extending methods. It makes it clear from local context what method you are extending (no need to dig through the source code after import statements).
Also, using infix definition is also scary in my opinion, especially in combination with importing,
julia> import Base.<
julia> a < b = 3 # typo, should have been ==
Error showing value of type typeof(<):
SYSTEM: show(lasterr) caused an error
TypeError(:parseint_preamble, "if", Bool, 3)
@DNF Ah, ok. No, I was just trying to point out the structure, and perhaps the example was too simplistic. I am sorry for posting this long code, I don’t know if in general people think that is helpful.
What I meant is something, lets say, like:
struct quadratic # structure of the data set
# ax^2 + bx + c
a::Float64
b::Float64
c::Float64
end
function F( f :: quadratic, x :: Float64 ) # F to evaluate the function at x
return f.a*x^2 + f.b*x + f.c
end
# the f1 < f2 operation will be true if there is x0 such
# that f1(x0) < f2(x) for all x.
function Base.:<( f1::quadratic , f2::quadratic )
if f1.a < 0. && f2.a > 0. # f1 is concave downward, but not f2
return true
end
xmin1 = -f1.b/(2*f1.a) # x that minimizes f1
xmin2 = -f2.b/(2*f2.a) # x that minimizes f2
if F(f1,xmin1) < F(f2,xmin2)
return true
end
return false
end
f1 = quadratic(1.,0.,0.)
f2 = quadratic(1.,0.,-1.)
println( f1 < f2 )
println( f2 < f1 )
Btw, I was using this pattern a lot when coming to Julia - and completely stopped using it over the time.
I try to avoid initializing empty structs and setting up the fields afterwards as much as I can by now.
It just doesn’t look as nice, fragments the initialization code of your struct making it harder to read, makes it impossible to use immutables - and worst of all: you may end up with garbage.
So I usually just define a couple of convenience constructors with reasonable default values - and usually I can stop right there since my use cases are covered.
If that’s not the case for you, the already mentioned https://github.com/mauro3/Parameters.jl ) seems like a good solution.
Got it! Make sense! Thank you! (I keep putting the examples, for ignorants like me).
mutable struct test
a :: Int64
b :: Float64
test(a,b) = new(a,b) # without keywords
test(;a=1,b=2.e0) = new(a,b) # with keywords (both work!)
end
x = test(a=4,b=5.e0)
println(x)
y = test()
println(y)
z = test(3,5.e0)
println(z)
That last example is very clean, but it led me to a think about the possible problems in being able to define a function with the same name of the struct. For example, in this case below, the result is that the first call to test results in defining x as of type test, but the second results in a function value.
Isn’t this problematic, as I could define a new function with the name of a previously defined struct from some module I am using, without knowing it?
(curiously, Julia returns an error if I try to define the struct after the function).
struct test
a :: Int64
end
function test(;a=3)
return a
end
x = test(2)
println(x)
> test(2)
x = test()
println(x)
> 3