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
会自动用唯一名称替换宏中的 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
这相当于用g
定义中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
我正在寻找一种优雅的方式来重新安排我的代码。对于开发求解器,您可以有很多具有相同设置的不同选项。例如,在高层次上,代码看起来像这样:
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
会自动用唯一名称替换宏中的 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
这相当于用g
定义中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