3

I am looking for an elegant way to re-arrange my code. For developing solvers, what happens is you can have a lot of different options which have the same setup. For example, at a high level the code looks something like this:

function solver()
  # Start by assigning a bunch of variables, preprocessing, etc.
  ...
  # Choose and execute solve
  if alg==1 doAlgorithm1()
  elseif alg==2 doAlgorithm2()
  elseif alg==3 doAlgorithm3()
  end
  # Postprocess and return
  ...
end

Previously when I quickly prototypes I put the solver algorithms right in the code. However, as I am tagging on more and more algorithms this is becoming messy (especially when some are hundreds of lines of code) and so I want to put those calls as a separate function. However, I want them to essentially be the same thing as putting the block of code there: access the same scope, have side effects, etc.

I thought about using a macro for this, but since they eval at the global scope it seemed like the wrong solution. Nested functions seem like they might be doable but I have to define them at the top of the solver (and my intention was to not do that to keep the high level algorithm readable) and there are issues with scoping a nested function within a nested function (for parts which repeat in only some of the algorithms!). I could just define this as another function without trying to keep the same scope but then it would be ugly with a long trail parameters (with each algorithm having the same parameters!)

What is a good way to organize this kind of code? Is there a more Julian approach to this problem?

Chris Rackauckas
  • 18,645
  • 3
  • 50
  • 81

3 Answers3

3

The way to do this is just to pass the solver function as a parameter to the solve function:

solver1(state) = "Solver 1 with state $state"

function solve(solver)

    # set up the state here, e.g. in a State object

    state = [1, 2]

    result = solver(state)

end

solve(solver1)

"Accessing the same scope" is the same as passing over a variable containing the local state you need. "Having effects" is the same as passing back variables from the solver method.

If the solver functions are sufficiently simple, they will be inlined by the compiler into the solve function, and it will be as if you had typed them in directly (if you were worried about the overhead of the function call).

EDIT: Didn't read carefully enough. The "long trail of parameters" you mention you can just store into a special type, e.g.

type SolverParams
    a::Int
    b::Float64
    params::Vector{Float64}
end

Then each solver takes an argument of this type. Or it could just be a tuple that you pass into the solver.

David P. Sanders
  • 5,210
  • 1
  • 23
  • 23
  • This result is similar to just passing all the variables into `method`, except putting them in a `State` object first. This works, and can be easy to do using Parameters.jl to pack/unpack the state object, but I was hoping to find a way such that the scope of `method` would just be the scope of `solve` to have it do all the passing in the background. – Chris Rackauckas May 21 '16 at 09:35
  • David, when you say "pass the solver name", I think it needs to be clear that you don't mean to pass the name as a string, for example, but rather a type, that can be dispatched on, correct? Also, if the `SolverParams` are not changed once created, it would probably be better to make that `immutable` instead of a `type`. – Scott Jones May 21 '16 at 10:14
  • I meant just pass the function name. I'll edit my answer when at a computer. – David P. Sanders May 21 '16 at 10:43
3

I am not sure if this is a better approach than using a state object, but you can use macros to achieve what you want:

macro f() 
    quote 
        b = 5
        x = a * b
        x  # the value of a block is equal to its last statement
    end
end

function foo()
    a = 2
    b = 3
    x = @f()
    x, b  # (10,3)
end

Note that Julia automatically replaces b and x within the macro with a unique name to avoid side effects. If you want to have side effects, you can use the following:

macro g() 
    esc(quote 
        b = 5
        x = a * b
    end)
end

function bar()
    a = 2
    b = 3
    @g()
    x, b  # (10,5)
end

This is equivalent to replacing @g() with the code between quote and end from the definition of g. One can also define a small convenience macro:

macro def(name, definition)
    return quote 
        macro $(esc(name))()
            esc($(Expr(:quote, definition)))
        end
    end
end

With that, g could have been defined as

@def g begin
    b = 5
    x = a*b
end
tim
  • 2,076
  • 8
  • 14
1

Since julia functions can modify their arguments, side effects can usually be handled by making whatever you want to modify one of the arguments of the function.

This demo uses anonymous functions to allow your solvers to take different parameters, if you want. I'm not sure it's exactly what you're asking, but if you don't know about this already it might be informative.

using Base.Test

function solver1(data, initialize::Bool)
    if initialize
        fill!(data, 0)
    end
    return 1
end
function solver2(data, hellostring)
    println(hellostring)
    return 2
end

function solver(f)
    data = [1,2,3,4]
    ret = f(data)
    println(sum(data))
    return ret
end

@test solver(data->solver1(data, false)) == 1
@test solver(data->solver1(data, true)) == 1
@test solver(data->solver2(data, "hello, world")) == 2

which generates the output

10
0
hello, world
10
tholy
  • 11,882
  • 1
  • 29
  • 42
  • That's not quite what I'm looking for, but thanks for the response. Instead I am looking for something like Tim posted. Basically, how to copy-paste a code (at parse time) that stored somewhere else, but more elegant than actually copy pasting it in there. – Chris Rackauckas May 23 '16 at 15:04
  • I guess another way of saying it is that I never wanted `doAlgorithm1()` to be a function, rather just a small loop that adds an extra feature some larger algorithm. 20 features later, the overarching algorithm is impossible to read, and there are 50 parameters to pass into each algorithm if I put them out into functions. – Chris Rackauckas May 23 '16 at 15:11