Julia:使用许多不同但相关的算法选择来构建代码

Julia: Structuring code with many different but related algorithm choices

我正在寻找一种优雅的方式来重新安排我的代码。对于开发求解器,您可以有很多具有相同设置的不同选项。例如,在高层次上,代码看起来像这样:

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

以前,当我快速制作原型时,我会将求解器算法直接放在代码中。然而,随着我在越来越多的算法上做标记,这变得很混乱(尤其是当一些算法有数百行代码时),所以我想将这些调用作为一个单独的函数。但是,我希望它们本质上与将代码块放在那里是一样的:访问相同的范围,有副作用等。

我考虑过为此使用宏,但由于它们在全局范围内求值,所以这似乎是错误的解决方案。嵌套函数看起来可能是可行的,但我必须在求解器的顶部定义它们(我的意图是不这样做以保持高级算法的可读性)并且在嵌套函数中限定嵌套函数的范围存在问题(对于仅在某些算法中重复的部分!)。我可以将它定义为另一个函数,而不试图保持相同的范围,但是如果使用长路径参数(每个算法都具有相同的参数!),它会很丑陋!)

组织这种代码的好方法是什么?是否有更 Julian 的方法来解决这个问题?

这样做的方法只是将求解器函数作为参数传递给求解函数:

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" 与传递包含您需要的本地状态的变量相同。 "Having effects" 与从求解器方法传回变量相同。

如果求解器函数足够简单,它们将被编译器内联到求解函数中,就像您直接输入它们一样(如果您担心函数调用的开销) .

编辑:没有仔细阅读。您提到的 "long trail of parameters" 可以存储为特殊类型,例如

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

然后每个求解器都采用这种类型的参数。或者它可能只是您传递给求解器的元组。

由于 julia 函数可以修改它们的参数,因此通常可以通过修改函数参数之一来处理副作用。

此演示使用匿名函数来允许您的求解器根据需要采用不同的参数。我不确定这正是您要问的,但如果您还不知道这一点,它可能会提供一些信息。

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

生成输出

10
0
hello, world
10

我不确定这是否比使用状态对象更好,但您可以使用宏来实现您想要的:

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

请注意,Julia 会自动用唯一名称替换宏中的 bx 以避免副作用。如果你想有副作用,你可以使用以下方法:

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

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

这相当于用g定义中quoteend之间的代码替换@g()。也可以定义一个方便的小宏:

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

这样,g 可以定义为

@def g begin
    b = 5
    x = a*b
end