Julia Metaprogramación: función para series matemáticas
Estoy tratando de construir una función que genere una expresión para ser asignada a una nueva función en memoria. Podría estar malinterpretando la capacidad de la metaprogramación, pero estoy tratando de construir una función que genere una serie matemática y la asigne a una función como:
main.jl
function series(iter)
S = ""
for i in 1:iter
a = "x^$i + "
S = S*a
end
return chop(S, tail=3)
end
Entonces, esto construirá el patrón y estoy trabajando temporalmente con él en la respuesta:
julia> a = Meta.parse(series(4))
:(x ^ 1 + x ^ 2 + x ^ 3 + x ^ 4)
julia> f =eval(Meta.parse(series(4)))
120
julia> f(x) =eval(Meta.parse(series(4)))
ERROR: cannot define function f; it already has a value
Obviamente, eval no es lo que estoy buscando en este caso, pero ¿hay otra función que pueda usar? ¿O no es ésta una forma viable de realizar la tarea en Julia?
Respuestas
El error real que obtiene no tiene nada que ver con la metaprogramación, sino con el hecho de que está reasignando f, al que se le asignó un valor antes:
julia> f = 10
10
julia> f(x) = x + 1
ERROR: cannot define function f; it already has a value
Stacktrace:
[1] top-level scope at none:0
[2] top-level scope at REPL[2]:1
Simplemente no le gusta eso. Llame a cualquiera de esas variables de manera diferente.
Ahora al problema conceptual. Primero, lo que haces aquí no es una metaprogramación "adecuada" en Julia: ¿por qué lidiar con cadenas y análisis? Puede trabajar directamente en expresiones:
julia> function series(N)
S = Expr(:call, :+)
for i in 1:N
push!(S.args, :(x ^ $i))
end
return S
end
series (generic function with 1 method)
julia> series(3)
:(x ^ 1 + x ^ 2 + x ^ 3)
Esto hace uso del hecho de que +pertenece a la clase de expresiones que se recopilan automáticamente en aplicaciones repetidas.
En segundo lugar, no llama evalal lugar apropiado. Supongo que quisiste decir "dame la función de x, siendo el cuerpo lo que series(4)regresa". Ahora, mientras lo siguiente funciona:
julia> f3(x) = eval(series(4))
f3 (generic function with 1 method)
julia> f3(2)
30
no es ideal, ya que compila el cuerpo cada vez que se llama a la función. Si hace algo así, es preferible expandir el código una vez en el cuerpo en la definición de la función:
julia> @eval f2(x) = $(series(4))
f2 (generic function with 1 method)
julia> f2(2)
30
Solo debes tener cuidado con la higiene aquí. Todo depende del hecho de que sepa que el cuerpo generado está formulado en términos de xy el argumento de la función coincide con eso. En mi opinión, la forma más juliana de implementar tu idea es a través de una macro:
julia> macro series(N::Int, x)
S = Expr(:call, :+)
for i in 1:N
push!(S.args, :($x ^ $i))
end
return S
end
@series (macro with 1 method)
julia> @macroexpand @series(4, 2)
:(2 ^ 1 + 2 ^ 2 + 2 ^ 3 + 2 ^ 4)
julia> @series(4, 2)
30
No quedan variables libres en la salida.
Finalmente, como se ha señalado en los comentarios, hay una función (y la macro correspondiente) evalpolyen Base que generaliza su caso de uso. Tenga en cuenta que esta función no usa la generación de código; usa una función generada bien diseñada, que en combinación con las optimizaciones da como resultado un código que generalmente es igual al código generado por macro.
Otra opción elegante sería utilizar el mecanismo de envío múltiple de Julia y enviar el código generado por tipo en lugar de valor.
@generated function series2(p::Val{N}, x) where N
S = Expr(:call, :+)
for i in 1:N
push!(S.args, :(x ^ $i))
end
return S
end
Uso
julia> series2(Val(20), 150.5)
3.5778761722367333e43
julia> series2(Val{20}(), 150.5)
3.5778761722367333e43
Esta tarea se puede lograr con comprensiones. Necesito RTFM ...
https://docs.julialang.org/en/v1/manual/arrays/#Generator-Expressions