Reputation: 334
Imagine a constructor that takes two arguments and initializes 3 named fields using the values of the two arguments. Something like this:
type test1
a
b
c
test1(a,b) = new(a,b,a/b)
end
This works fine, but what if the value for c is not such a simple expression? What if it runs over a line or two? Or is a complex list comprehension? Sticking the expression for c
directly into the new()
is unwieldy and makes the code harder to read (IMO). I'd rather do something like this:
type test1
a
b
c = a/b
test1(a,b) = new(a,b,c)
end
but a
and b
aren't defined until the call to test1(a,b)
apparently, so this doesn't work. Perhaps I'm just looking for syntactic sugar. In any case, I'd like to understand better when the values of the arguments of the constructor become known and if they can be used before the call to new().
Is there a better way (better than the first example) to do what I'm attempting to do in the second example?
(I think the following question and its answers are related enough to be helpful, but I'm still too much of a Julia newbie Building a non-default constructor in Julia)
Edited: At the risk of being too specific, I thought I'd include the actual use case where this question arose. I'm doing an adaptive integration scheme. Each volume element that straddles the integration boundary is further subdivided. My definition of the "cube" type is below. My student wrote a working prototype in python, but am trying to rewrite it in julia for the performance gain.
using Iterators
# Composite type defining a cube element of the integration domain
type cube
pos # floats: Position of the cube in the integration domain
dx # float: Edge length of the cube
verts # float: List of positions of the vertices
fvals::Dict # tuples,floats: Function values at the corners of the cube and its children
depth::Int # int: Number of splittings to get to this level of cube
maxdepth::Int # Deepest level of splitting (stopping condition)
intVal # float: this cube's contribution to the integral
intVal = 0
cube(pos,dx,depth,maxdepth) = new(pos,dx,
[i for i in product(0:dx:dx,0:dx:dx,0:dx:dx)],
[vt=>fVal([vt...]) for vt in [i for i in product(0:dx:dx,0:dx:dx,0:dx:dx)]],
depth,maxdepth,intVal)
end
Upvotes: 2
Views: 918
Reputation: 334
Using Matt B.'s answer I constructed the following answer for my specific use case. Using the block syntax for function is a lot cleaner.
using Iterators
# Composite type defining a cube element of the integration domain
type cube
pos # floats: Position of the cube in the integration domain
dx # float: Edge length of the cube
verts # tuple: List of positions of the vertices
fvals # floats: Function values at the corners of the cube and its children
depth::Int # int: Number of splittings to get to this level of cube
maxdepth::Int # Deepest level of splitting (stopping condition)
function cube(pos,dx,depth,maxdepth)
verts = [pos+[vt...].*dx for vt in product(0:1,0:1,0:1)]
fvals = [fVal([vt...]) for vt in verts ]
return new(pos,dx,verts,fvals,depth,maxdepth)
end
end
Upvotes: 0
Reputation: 31342
Inner constructors are just the functions that appear inside the type block with the same name as the type. That means that you can use either function syntax to define them. You can use the short form (as you did above), or you can instead use the more verbose block syntax:
type test1
a
b
c
function test1(a,b)
c = a/b
return new(a,b,c)
end
end
The call to new
doesn't even need to be the last expression in the method; you can assign its result to an intermediate variable and then return it.
A few more details: The type
block is just like a normal Julia scope with a few exceptions:
new
builtin to instantiate the type, and functions with the same name as the type become the inner constructor.When you put any other assignments inside the type block, they simply create a local variable within that scope. It's not a field or a part of the type, but simply a variable that can be used in the constructor's methods. That said, it's not very useful and will likely change in the future.
Upvotes: 6