I have a situation, where I have an integration and I want to give the integration limit as an argument to a function, but the limit depends on something that is calculated in the same function. Therefore, I would like to use a variable name as a function argument, but that variable is only defined in the function scope.
It should be doable with metaprogramming, but I can't figure it out.
I guess it is something analogous to making these function calls work
function foo(x)
bar = 1
baz = 2
eval(x)
end
foo(bar) # 1
foo(baz) # 2
At the moment this gives UndefVarError
.
You could use a dictionary or a named tuple and use symbols as keys. no need to eval
here.
Thanks!
In some cases, I would also like to do some simple manipulation with the variable and in that case the dictionary does not work. For example, foo(10*bar)
.
I think you should give a more realistic example of what you're trying to do. It doesn't seem a case for metaprograming at all, but your example foo(x)
function does not make sense, as x
, is the argument. You example works with:
julia> function foo(x)
if x == :bar
1
elseif x == :baz
2
end
end
foo (generic function with 1 method)
julia> foo(:bar)
1
julia> foo(:baz)
2
or
julia> function foo(f)
f()
end
foo (generic function with 1 method)
julia> foo(() -> 1)
1
julia> foo(() -> 2)
2
but understanding why this is not what you need requires some more explanation.
The actual use case is that I am calculating the mass of galaxy groups, which includes taking an integral to some radius from the group's center. There are different definitions of radii that can be used as the radius of the group. I want to be able to give the main function an argument, which determines the upper limit of that integral. The problem is that the different radii are not known beforehand. They are calculated in the main function. For example, I would like to run a function calculate_mass(galaxies; integral_limit=10*R1)
or change the limit to integral_limit=5*R2
. In the end, R1
and R2
will have some value, and the upper limit will be used as an argument for the quadgk
function.
Fortunately, at the moment, I am only comparing two different radii and implementing it as a simple boolean. I only need multiplication, so I added another argument to the calculate_mass
function that contains the factor as a number. I just hoped there was an elegant way to solve this so that I could have an argument 10*R1
without R1
being defined yet (but it will be determined before it is actually used).
Does this make any sense?
if there are not a lot of additional usage instances, you could just define a Radius{N}
struct, define Base.:(*)(::Number, ::Radius)
and then have a method that evaluates it once it's known (kind of like a minimal symbolics stack)
so something like
struct Radius{N}
scale::Float64
end
Base.:(*)(s::Number, r::Radius{N}) where {N} = Radius{N}(r.scale * s)
const R1 = Radius{1}(1.0)
const R2 = Radius{2}(1.0)
Then
julia> 10 * R1
Radius{1}(10.0)
then resolve it once the value is known as resolve(r::Radius{1}, r1, r2) = r1 * r.scale
and resolve(r::Radius{2}, r1, r2) = r2 * r.scale
(if there are two options, otherwise you could have all of them as a tuple and define all of them with a single method)
Though I just read the post where you said you wanted to do some manipulation as foo(10*R1)
, which I guess discards my proposal because you'd basically be reinventing a symbolics engine (or maybe it just works if you define another struct with the function and the Radius{N}
as fields, for delayed evaluation? In any case hard to assess without the details)
I am slipping up with trying to simplify things :grinning: Using just R1
and R2
wasn't the best example because the radii actually have names (such as R_grav
and R_200
). I like your solution, but I guess I would just have to put the radii into some ordered list. I think in general, there is no point in trying to figure out more complicated solutions, as my case was quite simple in practice and solvable with just a boolean and an extra argument, and it is unlikely to get any more complicated.
I guess the question I was looking an answer to was whether I could have an expression 10*x
, when x
is not defined, and evaluate it after x
has been defined.
yeah, if a flag works then it's probably the best solution instead of adding code (you could still build R_grav = Radius{:grav}(1.0)
if you really really wanted to, though)
Why aren't these just separate arguments to calculate_mass
?
i.e.
calculate_mass(galaxies; integral_limit=10, limit_style=:R_grav)
or something?
I guess the question I was looking an answer to was whether I could have an expression 10*x, when x is not defined, and evaluate it after x has been defined.
You sort of can, but it's a terribly bad idea in many ways.
julia> function foo(x)
global bar = 1
global baz = 2
eval(x)
end
foo (generic function with 1 method)
julia> foo(:(10 * bar))
10
please don't do this in real code
why can't the radii calculations be refactored into a separate function? :thinking:
Is there a reason to not let the user pass in a function that computes the radius, rather than an expression?
I guess in the general case you just want to pass a function:
julia> function foo(f)
x = 2
f(x)
end
foo (generic function with 1 method)
julia> foo(x -> 10*x)
20
(oh yes, everybody just said that)
Mason Protter said:
i.e.
calculate_mass(galaxies; integral_limit=10, limit_style=:R_grav)
or something?
This is what I ended up doing.
Sergio Vargas said:
why can't the radii calculations be refactored into a separate function? :thinking:
It is an iterative calculation that cannot be calculated beforehand as the integral has to be taken in every iteration until the results have converged.
Fredrik Bagge Carlson said:
Is there a reason to not let the user pass in a function that computes the radius, rather than an expression?
In general, I agree, but these two radii that I wanted to compare are both needed in the calculations in different steps and are already calculated.
Multiplying two numbers takes on the order of a nanosecond, are you sure that recalculsting them is an issue? Even if you pass in a function and that function is called twice, julia will likely inline that function and optimize away any simple calculation that is repeated. How expensive is this operation we're talking about?
They aren't expensive computationally, and I could definitely just calculate them twice, once for the general calculations and once for the integral limit. I think I figured that reusing the values would be simpler than doing the radius calculations twice, but that does not seem to be the case.
Last updated: Dec 28 2024 at 04:38 UTC