Metaprogramming - defining a function

metaprogramming

#21

This looks much better. I will give it a try.


#22

Very high level comment. Some people come to Julia from other dynamic languages and want to call eval inside of a function in order to do something specified by some end user in a dynamic way. Instead, the right approach in Julia is to splice a bit of end-user-specified code into a function definition, evaluate that function definition and then call that function as needed. The trick is to move the eval call outward. Hopefully that’s conceptually helpful.


#23

I know the problem is how I am addressing Julia. For me “it feels bad” moving stuff outside. It feels like polluting the global space.

I will see tomorrow how it looks in my case.

Thanks for all your advices. They are all welcomed.


#24

I have a question about a modification of Kristopher’s code and “world age” errors. See code below:

function create_function_expr(function_name, sig, variables, body)
    Expr(:function, 
	Expr(:call,
		function_name,
		[Expr(:(::), s, t) for (s, t) in zip(variables, sig)]...),
	body
)
end

function makefunc()
    fname = :foobar
    sg = (Int, Float64, Int32)
    vars = [:a, :q, :d]
    bdy = :(a + q * d)
    return eval(create_function_expr(fname, sg, vars, bdy))
end

function runfunc()
    f = makefunc()
    println("within a function: ", f(1, 2.0, Int32(3)))
end


# runfunc()

f = makefunc()
println("from global scope: ", f(1, 2.0, Int32(3)))

runfunc()

In this code, running runfunc() after the call from global scope works, and calling runfunc before the global call does not. Is this due to when the runfunc function is first compiled? Why?


#25

See https://docs.julialang.org/en/v1/manual/methods/#Redefining-Methods-1 which describes this quite well.


#26

Thanks. Yet another metaprogramming gotcha I see :).


#28

Don’t do this. If this is for private (package internal) use, just use eval, it’s much better and easier. If this is for external use, then this way of using esc incorrectly will guarantee you macro hygiene bug.


#29

Sure, I should have commented more on escaping. I just wanted him to know this pattern of putting a function that retuns an expression into a macro, and that then you don’t need eval.

Althouhg esc the whole expression in not what you want most of the time, it is used a lot in thin kind of small examples.


#30

And there’s absolutely nothing wrong with eval.

And that’s a wrong pattern. If you want/need a macro, don’t even start this way. If it’s just to automatically create functions internally, don’t use macro.


#31

I am still not managing to do what I want. I am calling an already existing function from within the newly created function. The existing function complains about receiving a ::Symbol instead of a value.

I am sure it is my wrong doing, but I got the feeling that Julia is getting in the middle, not allowing me to do what want in this particular task.

I really miss an easy way to refer to the symbol content in the context of the function, so I can use the symbol or its content as I need. Maybe this could be an enhancement to Julia. (Regardless that for sure there are plenty of good technical reasons why this is not good or possible).

Regards


#32

What I mean is that sometimes you don’t want to use eval because it is global scope only, you can use a macro when you need something local.

I disagree, this is better to debug and reason about, specially if you are still learning, and also, it is a pattern indeed, seen in many places, and suggested by many (smart) people (that tought me it), I’m not just inventing stuff.

So you never ever do this function insede macro? How do you do it? I used to do only macros, until Total Verb and others told me it’s better to do it this way, now you say, we shouldn’t.

Perhaps if the documentation had more examples we could agree.

I agree, that the function in a macro in not needed in this case, but I was trying to show him how you can do it that way.

Hope I didn’t confuse you Jose, pasa un buen dia!


#33

With trivial modifications to my code aobve

body = :(a + q * other_function(d))
other_function(x) = (println("Calling this function from inside"); x^5)
julia> eval(create_function_expr(function_name, sig, variables, body))
foobar (generic function with 1 method)

julia> foobar(1, 2.0, Int32(3))
Calling this function from inside
487.0

Hmm, yes maybe you are just doing it wrong. Without a clear example of what you actually want to do, it is hard to say.


#35

This is defining a global function.

It’s absolutely wrong to learn the wrong pattern. You can call the macro as function for debugging just fine.

I know and I’m really sad to see that. It’s absolutely a bad idea to do it. I’ve seen many issue caused by it and
I recommand against it everytime I see it. I’m not saying that you invented it, but you still shouldn’t do it.

I didn’t say you shouldn’t call function in macros, that’s just code organization and when you have something large that’s useful and that’s totally fine. I said your use of esc is completely wrong though.


#36

I have put the code here. Really ugly because I am not a developer, I am new to julia and there is plenty of different tries already commented.

It requires to have vapoursynth installed.

Vapoursynth (like Avisynth) allows editing videos by means of scripting. They allow doing things like this.

Avisynth has an scripting language AVS. Vapoursynth opted to python. Both of them uses C for the plugins.

This is where Julia looks like a good fit: using one language for both scripting and plugins.

Somebody had the same idea using Lua -another good fit- (see luasynth).

Being able to access all those already existing plugins for video edition would be great.


#37

One thing that I have observed is that when I print the declaration of the function I get something like:

function SetLogLevel(level::Int64)
    #= /home/jose/src/VapourSynth.jl/src/vsplugins.jl:405 =#
    vsmap = createMap()
    #= /home/jose/src/VapourSynth.jl/src/vsplugins.jl:406 =#
    for (t, s, ex, tipo, mandatory) = Any[("level", :level, :(level::Int64), Int64, true)]
        #= /home/jose/src/VapourSynth.jl/src/vsplugins.jl:407 =#
        println(s)
        #= /home/jose/src/VapourSynth.jl/src/vsplugins.jl:409 =#
        if tipo <: Int
...

It can be observed that in the function signature it appears level (not :level) which is correct. But on the function body, it appears :level instead of level, which doesn’t seem correct. I don’t know why this is happening. I have made the following minimal example which behaves properlly:

function create_function(funcname::String )
    fname = Symbol(funcname)

    #println(var)
    # Creating function signature
    spath = Symbol("var")
    #spath = [Symbol("var")]
    arg = Expr(:kw, spath, String)
    f_call = Expr( :call,
                   fname,
                   arg )
    f_body = quote
        #tmp = $spath
        var = ccall((:getenv, "/lib/libc-2.28.so")
                     , Cstring, (Cstring,)
                     , $spath)
        return unsafe_string(var)
    end
    f_declare = Expr( :function, f_call, f_body )
    println(f_declare)
    eval(f_declare)
end


#params = [("path", String)]
create_function("testing")
println( testing("SHELL") )

that when executed it prints:

$ julia example.jl 
function prueba(var=String)
    #= /home/jose/src/example/example.jl:14 =#
    var = ccall((:getenv, "/lib/libc-2.28.so"), Cstring, (Cstring,), var)
    #= /home/jose/src/example/example.jl:17 =#
    return unsafe_string(var)
end
/bin/bash

It can be observed that in the minimal example, never appears :var.

Does anybody see what I am doing wrong in function create_function?
The function signature looks good, so:

  • I would say that it is fine at least until line 351.
  • Everything is commented until L405. This line creates a VapourSynth key/value list.
  • L406. This is the interesting one:

Why does it produce?

for (t, s, ex, tipo, mandatory) = Any[("level", :level, :(level::Int64), Int64, true)]

instead of:

for (t, s, ex, tipo, mandatory) = Any[("level", level, :(level::Int64), Int64, true)]

The same symbol is created in L316 and used for both the function signature and the body.

See the :level vs level


#38

SOLVED

Just to say that I finally found the problem. (Just in case somebody is bothering with my code).

The problem seems to be due to having the symbols directly within the quoted section. The symbols get “quoted” themselves, so they evaluate again to symbols. I will try to post something later on when it gets clearer in my mind so this error I made might help others.


#39

Yes, for example

julia> dump(:( :sym ))
QuoteNode
  value: Symbol sym

julia> eval(QuoteNode(:sym))
:sym