As some of you might know, my diploma thesis is on simulation with the help of an internal ruby DSL. This DSL will be - at least I hope so - full of small little interesting things, making the life of those who use the DSL (i.e. biometricians) easier. This requires some interesting stuff on ruby meta programming, that will be posted - as done - in this blog. One of them is a thing I call “multiple parameters”. So what is it all about?
First of all we have a method in the DSL:
1 2 3 4 5 6 7 8 |
|
The intersting thing happens when you call the N function. If you just call it with simple numeric arguments, you get back a normal distributed sampler. If you call it with an array as one or more arguments, you get back the multiple normal distributed samples - constructed from the cartesian product of all params. This means, the treatment will get the Array as if you called:
1
|
|
The idea behind this is, that if you specify those multiple parameters for the simulation, the simulation is run multiple times - each time with a sligly different set of definitions.
A direct implementation wouldn’t be to hard - at least we are doing ruby. But as this is not only related to the N method, but to a lot of other things as well, I wanted to extract this rather into a model so I could write the N method something like this:
1 2 3 4 5 6 7 |
|
The MultipleParameters module is implemented following the path, Dave Thomas led in his 5th Episode of “The Ruby Object Model and Metaprogramming” for memorization. I’ll try to explain the module but first have the implementation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
So this is a class-level module (i.e. you need to get access to class methods rather than object methods) so you extend your class/module with it rather than including it. After having done this, you can call the mutate_params method for any previously defined method. When called, your originally defined method will get boxed into an object (line 3) and it will be redefinded. The redefined method will create the cartesian product of the argumenes (i.e. ([0,1], [1,2]) yields to (0,1), (0,2), (1,1) and (1,2)) and than create an array of all the results from the calls. Before beeing able to call the originally defined method you have to bind the method to the current self (line 24), because the method will be called as an object method, and than passing all different parameter sets to it.
Finally you have an array of all return values which is returned. The last line is somewhat of convinience as it returns the object directly if you did not specify any multiple parameters.
Note: This approach does not work, if you original method needs an array to be passed. I will probably extends the approach in the future to work with ranges as well.