Creating a type to store parameters in, and constructors with keyword arguments

213 views
Skip to first unread message

Andrew

unread,
Jan 16, 2015, 7:34:19 PM1/16/15
to julia...@googlegroups.com
Suppose I have a model which contains many parameters. I'd like to store my parameters in a type, for example

type Parameters
 sigma
::Real
 xi
::Real
 eta
::Real
 beta
::Real
 rho
::Real
 agrid
::FloatRange
end


and then I need to assign some values to my parameters. The natural way I see to do this is

params = Parameters(1,2,3,4,5,linrange(1,10,10))



or something like that. However, the fact that I need to remember the order in which I defined these parameters means there is some chance of error. In reality I have about 20 parameters, so defining them this way would be quite annoying.

It would be nice if there was a constructor that would let me use keyword arguments, as in

params = Parameters(sigma=1,xi=2,eta=3,beta=4,rho=5,agrid=linrange(1,10,10)) .



I know I could write my own constructor and use keyword arguments, but then I think I'd still need to use the ordered constructor to write that one.

Is there an easy way to do this? Maybe a macro that could automatically define a constructor with keyword arguments?(I don't know much about metaprogramming). Alternatively, is there is a cleaner way to store parameters that doesn't use types?

---
I did find a related post here. https://groups.google.com/forum/#!searchin/julia-users/constructor$20keyword$20arguments/julia-users/xslxrihfO30/jV2awP5tbpEJ . Someone suggests that you can define a constructor like,
Foo(;bar=1, baz=2) = new(bar, baz)

which does what I want. Is there a way to macro that so that it's automatically defined for every field in the type?

Simon Danisch

unread,
Jan 17, 2015, 7:02:50 AM1/17/15
to julia...@googlegroups.com
There are plans, to integrate this as an default constructor:
Best is to write the constructor yourself, to have the correct defaults ;)
By the way, your type is not designed very well, as you're using abstract field types.
Better would be:

type Parameters{RealT <: Real, RangeT <: FloatRange}
 sigma
::RealT
 xi
::RealT
 eta
::RealT
 beta
::RealT
 rho
::RealT
 agrid
::RangeT
end

see: http://julia.readthedocs.org/en/latest/manual/performance-tips/  => Avoid containers with abstract type parameters

Mauro

unread,
Jan 17, 2015, 7:08:24 AM1/17/15
to julia...@googlegroups.com
There is an issue for keyword constructors:
https://github.com/JuliaLang/julia/issues/5333
and a related pull request:
https://github.com/JuliaLang/julia/pull/6122

Anyway, it should be possible to make a macro for that but I'm not aware
of an available one. Alternatively you can use a Dict. Also, breaking
up the parameters in sub-parameter types might make it a bit more
managable, i.e. 4x5 parameters instead of 20 parameters in one type.

Related is packing and unpacking of parameters:
https://groups.google.com/forum/#!searchin/julia-users/macro$20parameter/julia-users/IQS2mT1ITwU/vrE9FCaQH6EJ

Greg Plowman

unread,
Jan 17, 2015, 6:08:42 PM1/17/15
to julia...@googlegroups.com

Not sure if this is helpful.
Not sure if it is a good idea in general.
Certainly unsure if it is Julian.

However, I find it useful because I can change fields of my type (reorder, add, remove, rename etc) quite easily.


macro CallDefaultConstructor(T)
    expressions = [ :($field) for field in names(eval(T)) ]
    return Expr(:call, T, expressions...)
end

type Parameters
   sigma::Real
   xi::Real
   eta::Real
   beta::Real
   rho::Real
   agrid::FloatRange
end

function Parameters()
    eta = 3
    sigma = 1
    rho = 5
    xi = 2
    agrid = linrange(1,10,10)
    beta = 4
   
    @CallDefaultConstructor Parameters
end

p = Parameters()

Andrew

unread,
Jan 17, 2015, 10:03:29 PM1/17/15
to julia...@googlegroups.com
Thanks, lots of helpful stuff here. It's nice that this was being considered for inclusion by default. I will investigate not using abstract field types. I also like the idea to just use multiple subtypes, which would be good for organization especially if the model got more complex.

Greg, I like your macro. It helped me understand metaprogramming some more. Also, the use of a function which defines the parameters explicitly is very similar to how I do this in MATLAB. 

I do have one question on your code for the macro. You write [ :($field) for field in names(eval(T)) ]. I played around with this and got

In [225]:
[ :($field) for field in names(eval(Parameters)) ]
Out[225]:
6-element Array{Any,1}:
 
:sigma
 
:xi  
 
:eta  
 
:beta
 
:rho  
 
:agrid
In [226]:
[ field for field in names(eval(Parameters)) ]
Out[226]:
6-element Array{Any,1}:
 
:sigma
 
:xi  
 
:eta  
 
:beta
 
:rho  
 
:agrid

In [231]:
names(eval(Parameters))
Out[231]:
6-element Array{Symbol,1}:
 
:sigma
 
:xi  
 
:eta  
 
:beta
 
:rho  
 
:agrid

Is there a reason you went through the trouble of using a comprehension? I think just 
expressions = names(eval(T))
does the same thing.


Greg Plowman

unread,
Jan 17, 2015, 10:51:11 PM1/17/15
to julia...@googlegroups.com

Good pickup Andrew.

Seems a bit redundant to use comprehension here. More concise might be:

macro CallDefaultConstructor(T)
   
Expr(:call, T, names(eval(T))...)
end


I just copied from part of a larger set of utilities that I use for managing composite types, where I tend to keep the same design pattern.


function CompositeCopyConstructor(T::Symbol)
    expressions
= [ :(copy(x.$field)) for field in names(eval(T)) ]
    body
= Expr(:call, T, expressions...)
   
    quote
       
function $T(x::$T)
           
return $body
       
end
   
end
end


function CompositeBinaryOp(T::Symbol, op::Symbol)
    expressions
= [ :($op(x1.$field, x2.$field)) for field in names(eval(T)) ]
    body
= Expr(:call, T, expressions...)
   
    quote
       
function $op(x1::$T, x2::$T)
           
return $body
       
end
   
end
end


function CompositeInPlaceUnaryOp(T::Symbol, op::Symbol)
    expressions
= [ :($op(x.$field, x1.$field)) for field in names(eval(T)) ]
    body
= Expr(:block, expressions...)
   
    quote
       
function $op(x::$T, x1::$T)
            $body
           
return x
       
end
   
end
end


function CompositeInPlaceBinaryOp(T::Symbol, op::Symbol)
    expressions
= [ :($op(x.$field, x1.$field, x2.$field)) for field in names(eval(T)) ]
    body
= Expr(:block, expressions...)
   
    quote
       
function $op(x::$T, x1::$T, x2::$T)
            $body
           
return x
       
end
   
end
end


function CompositeIsEqual(T::Symbol, op::Symbol)
    expressions
= [ :($op(x1.$field, x2.$field)) for field in names(eval(T)) ]
    body
= expressions[1]
   
   
for i = 2 : length(expressions)
        body
= Expr(:&&, body, expressions[i])
   
end
 
    quote
       
function $op(x1::$T, x2::$T)
           
return $body
       
end
   
end
end

Simon Danisch

unread,
Jan 18, 2015, 5:55:55 AM1/18/15
to julia...@googlegroups.com
This seems to be more useful(depending on your use cases):
function Parameters(
        eta = 3,
        sigma = 1,
        rho = 5,
        xi = 2,
        agrid = linrange(1,10,10),
        beta = 4
    )
    @CallDefaultConstructor Parameters
end
Like this, you can replace parts of the defaults ;)
And if you're at it, you could just have a macro like this
@with_keywordconstructor begin
immutabe Parameters{T <: Real}
sigma::T = 1
xi::T = 2
...
end
end

And If you're already at it, you could write a macro, which lets you define defaults for every field

Am Samstag, 17. Januar 2015 01:34:19 UTC+1 schrieb Andrew:
Reply all
Reply to author
Forward
0 new messages