Architecture 朱莉娅:用许多不同但相关的算法选择构造代码

Architecture 朱莉娅:用许多不同但相关的算法选择构造代码,architecture,structure,julia,Architecture,Structure,Julia,我正在寻找一种优雅的方式来重新安排我的代码。对于开发解算器,您可以有许多具有相同设置的不同选项。例如,在较高级别上,代码如下所示: function solver() # Start by assigning a bunch of variables, preprocessing, etc. ... # Choose and execute solve if alg==1 doAlgorithm1() elseif alg==2 doAlgorithm2() elseif

我正在寻找一种优雅的方式来重新安排我的代码。对于开发解算器,您可以有许多具有相同设置的不同选项。例如,在较高级别上,代码如下所示:

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
以前,当我快速创建原型时,我将解算器算法正确地放入代码中。然而,随着我对越来越多的算法进行标记,这变得越来越混乱(特别是当一些算法有数百行代码时),因此我想将这些调用作为一个单独的函数。然而,我希望它们本质上与将代码块放在那里是一样的:访问相同的范围,产生副作用,等等

我曾想过使用宏来实现这一点,但由于它们在全局范围内进行评估,因此似乎是错误的解决方案。嵌套函数似乎是可行的,但我必须在解算器顶部定义它们(我的目的是不这样做以保持高级算法的可读性),并且在嵌套函数中确定嵌套函数的范围存在问题(对于仅在某些算法中重复的部分!)。我可以将其定义为另一个函数,而不尝试保持相同的作用域,但如果有长的跟踪参数(每个算法都有相同的参数),则会很糟糕


组织这种代码的好方法是什么?对于这个问题是否有一种更为儒略的方法?

方法是将解算器函数作为参数传递给解算函数:

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)
“访问同一范围”与传递包含所需局部状态的变量相同。“具有效果”与从解算器方法传回变量相同

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

编辑:读得不够仔细。您提到的“参数的长轨迹”可以存储到一个特殊类型中,例如

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
会自动用唯一的名称替换宏中的
b
x
,以避免副作用。如果希望产生副作用,可以使用以下方法:

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

function bar()
    a = 2
    b = 3
    @g()
    x, b  # (10,5)
end
这相当于用
quote
end
之间的代码替换
@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

此结果类似于将所有变量传递到
方法
,只是首先将它们置于
状态
对象中。这很有效,使用Parameters.jl打包/解包state对象很容易,但我希望找到一种方法,使
方法
的范围正好是
求解
的范围,让它在后台完成所有传递。David,当你说“传递解算器名称”时,我认为需要明确的是,您的意思不是以字符串的形式传递名称,而是一种可以发送的类型,对吗?另外,如果
SolverParams
在创建后没有更改,那么最好将
设置为不可变的
,而不是
类型
。我的意思是只传递函数名。我会在电脑前编辑我的答案。这不是我想要的,但是谢谢你的回复。取而代之的是,我正在寻找类似Tim posted的东西。基本上,如何复制粘贴存储在其他地方的代码(在解析时),但要比实际复制粘贴更优雅。我想另一种说法是,我从不希望
doAlgorithm1()。20个特性之后,总体算法就不可能被读取了,如果我把它们放进函数中,每个算法都有50个参数要传递。我喜欢它,还有方便的宏。我只是在我的代码上试过,效果很好。