# A method to remove the use of runtime eval and invokelatest, as well as support closures in generated functions! Come with an implemented proptype

**URL:** <https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450>\
**Category:** Package Announcements\
**Tags:** metaprogramming\
**Created:** [August 12, 2019, 11:26am UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450 "2019-08-12T11:26:10Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![thautwarm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/thautwarm/32/37760_2.png) [@thautwarm](https://discourse.julialang.org/u/thautwarm)\
**Post date:** [August 12, 2019, 11:26am UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/1 "2019-08-12T11:26:10Z")

</div>

Hi, every one, we’ve made something very, very useful and powerful today(

You can check [GG.jl](https://github.com/thautwarm/GG.jl) for what we’ve provided.  
It’s originally motivated by solving an issue of @cscherrer 's [Soss.jl](https://github.com/cscherrer/Soss.jl): [Find a way around `invokelatest` · Issue #23 · cscherrer/Soss.jl · GitHub](https://github.com/cscherrer/Soss.jl/issues/23)

What we’re bringing about today are

1. the capability of creating functions in runtime without `eval` or introducing a world age problem.
2. the capability of defining closures in generated functions.

Following sections are the article about how we achieved above expected capabilities. And the latest article can be found at [GeneralizedGenerated.jl/slides/GG.md at master · JuliaStaging/GeneralizedGenerated.jl · GitHub](https://github.com/thautwarm/GG.jl/blob/master/slides/GG.md).

* * *

# Background

## Generated Functions in Julia

Julia can only create functions statically. Accurately, apart from using the `@generated` macro(generated functions), we can create functions

- literally for each function, or
- by calling macros

These 2 means only work when building a module, however  
once a module got built, no way to create a new function  
in current [world age](https://discourse.julialang.org/t/world-age-problem-explanation/9714).

However, there’re still cases that we have to generate functions  
according to runtime data, hence Julia has provided a solution called  
the [generated function](https://docs.julialang.org/en/v1/manual/metaprogramming/#Generated-functions-1).

The name of the generated function doesn’t suggest its power. I used to introduce this to  
the fans of DrRacket, they immediately responsed, “generating functions is trivial, exactly the most common sight in LISP idioms”. Though it was not their fault, they just tried to understand it literally, but I  
do feel like to laugh them at the street 🙂

In other dynamic languages, when we generate functions from runtime data, we must have following steps:

- invoke the generator with runtime data as its parameters, and bind the expression to a symbol to represent the generated function.  
The generator might build an AST(abstract syntax tree) which represents the generated  
function, and compile/execute it to a runtime object.

- calling the generated function at each expected callsite.

However, generated functions in Julia are more convenient, we only need to return the AST of the generated function, and compiler will do the remaining tasks for us.

**This, Julia’s generated function, becomes extremely useful when we need a polymorphic generated function**.

Polymorphisms can be achieved by **generics** or **templates**. The former one, will use the same code  
to process parameters of various types, while the latter provides specialized code for each group  
of the parameter types. Usually, the former is more dynamic, slow(for requiring runtime coercions) and limited(might disallow polymorphisms for specific types, like value types in Java), while the latter is  
static, fast, flexible.

Julia’s generated function achieves polymorphism via templates, and it can take advantage of  
static information to decide which specialized codes will be used in the callsite. Thus, we simply  
achieve the free-lunch polymorphic generated functions, **without manually managing the construction of each specialized function, or choosing a mechanism to decide which specialized function to use in the callsite**.

Julia’s generated function is indeed a miracle, personally I’d call it _Zero-Cost-Staging_.

## The Restrictions of Generated Function

The generated functions perform code generation based on the types of parameters.

```julia
function add(x::T, y::T)
    if T <: Number
        :(x + y)
    else if T <: AbstractString
        :(x * y) # '*' is string concatenation in Julia
    else
        throw("fatal")
    end
end

```

### “Typeability”? What Can Functions Be Generated From?

The first restriction is, we can only generate functions based on typeable data.

**Typeable** here is not an official term, I use this to indicate the data  
that can be held in the type parameters, you can roughly treat it as **immutable** , but  
the scope of the former is smaller than the latter.

However, although Julia is a dynamic language,  
a Julia type should be immutable datum. Which means  
we cannot do code generation based on things like  
`String`s(until Julia 1.2.x), mutable states, etc.

However, this is not a challenge at all. In terms of  
every domain specific problem, **we can demonstrate a  
form to express all non-typeable components**. Since that,  
we can easily transform a non-typeable data to typeable  
representations, and the reverse is also true.

Fortunately, this problem got solved when working with Chad for his  
[Soss.jl](https://github.com/cscherrer/Soss.jl). We wanted to avoid  
evaluating ASTs in runtime and using `invokelastest`, and we then  
achieved it by holding something equivalent to AST of the expected  
generated function in type parameters.

For instance, if we want to make Julia’s AST types typeable, there’s  
a solution,

```julia
abstract type TypeLevel end
struct TLCons{Hd, Tl} <: TypeLevel end
struct TLNil <: TypeLevel end
struct TLVal{Val} <: TypeLevel end
struct TLSExp{Fn, Args} <: TypeLevel end

function typelevellist(l)
    foldr(l, init=TLNil) do each, prev
        TLCons{each, prev}
    end
end

function expr2typelevel(x)::Type{TypeLevel}
    # omit
end

function interpret(t::Type{TypeLevel})
    # omit
end

```

and you can check the whole implementation [here](https://github.com/cscherrer/Soss.jl/blob/4733a8e5083deaffbc893ab7e6bca7b122eff54a/src/utils.jl#L455-L514).

### Purity? What Can Generated Codes Be?

The generated codes cannot be impure(with side effects), but the compiler is really conservative.

Say, we cannot use loal functions…

```julia

julia> @generated f() = :(() -> 1)
f (generic function with 1 method)

julia> f()
ERROR: generated function body is not pure. this likely means it contains a closure or comprehension.
Stacktrace:
 [1] top-level scope at none:0

```

Currently, the generate code cannot be a function, nor a generator, which is clarified in the document of  
Julia v1.1.1.

> Due to an implementation limitation, this also means that they currently cannot define a closure or generator.

So this is still unresolved problem, and exactly is what we will mainly discuss in this article.

# The Runtime Function Generation

## Closure Conversions

In the sense of intuition, when we want to do what a specific function will do,  
we’re considering constructing this function. However, it’s not the only manner.

Firstly let me simply introduce the closure conversions,

Given following codes,

```julia
function f(x, y)
    function g(z)
        x + z
    end
    g
end

```

We can find there’s a free variable `x` in the inner function `g`. To eliminate  
free variables, if we presume

- all free variables are readonly and,
- the closure function isn’t recursive,  
we can introduce a structure(`Closure`) to represent a closure:

```julia
struct Closure{F, Free}
    frees :: Free
end

function global_g((x, ), z)
    x + z
end

function (closure::Closure{F, X})(args...; kwargs...) where {F, X}
    F(closure.frees, args...; kwargs...)
end

function f(x, y)
   Closure{global_g, typeof(x)}(x)
end

```

The automation of above transformations is very simple to implement,  
if you already have tools to analyse the scopes.

Say, we can have such a function called `scoping`,

```julia
scoping(
    :(function f(x, y)
        function g(z)
            x + z
        end
    end)
) == Expr(
    :scope,
    (:x, :y, :g), # locals/bounds
    (), # free variables
    (), # globals
    inner_expr
)

```

And inside the `inner_expr`, for function `g`, we  
will have a scope expression:

```julia
Expr(
    :scope,
    (:z, ), # bounds
    (:x, ), # free variable
    (), # globals
    inner_expr2
)

```

Now, we point out that every scope expression can  
produce one or more global function pointers by following  
procedures:

- if a scope expression contains no free variables, do nothing,
- otherwise, for expression `Expr(:scope, bounds, frees, globs, expr)`,  
we generate a closure structure with `frees`:

```julia
struct Closure{F, Free}
    frees :: Free
end

function (closure::Closure{F, X})(args...; kwargs...) where {F, X}
    F(closure.frees, args...; kwargs...)
end

function split_args_kwargs(args)
    i_ = findfirst(x -> Meta.isexpr(x, :parameter), args)
    i = i_ === nothing ? 0 : i_
    (args[i+1:end], args[1:i])
end

# impl for closure conversion
function mk_closure_static(expr, toplevel::Vector{Expr})
    rec(expr) = mk_closure_static(expr, toplevel)
    @match expr begin
        # main logic
        Expr(:scope, _, frees, _, inner_expr) =>
            let closure_arg = :($(frees...), ),
                name = "",
                args = Symbol[]

                @match inner_expr begin
                    Expr(:function, :($name($(args...), )), body) ||
                    # (a, b, c, ...) -> body / function (a, b, c, ...) body end
                    Expr(:-> || :function, Expr(:tuple, args...), body) ||
                    # a -> body
                    Expr(:-> || :function, a::Symbol, body) && Do(args=[a]) =>
                        let glob_name = gensym(name),
                            (args, kwargs) = split_args_kwargs(args),
                            body = rec(body)

                            (fn_expr, ret) = if isempty(frees)
                                fn_expr = Expr(
                                    :function,
                                    :($glob_name($(args...); $(kwargs...))),
                                    body
                                )
                                (fn_expr, :glob_name)
                            else
                                fn_expr = Expr(
                                    :function,
                                    :($glob_name($closure_arg, $(args...); $(kwargs...))),
                                    body
                                )
                                ret = :(let frees = $closure_arg
                                    $Closure{$glob_name, typeof(frees)}(frees)
                                end)
                                (fn_expr, ret)
                            end

                            push!(toplevel, fn_expr)

                            if name == "" # anonymous function
                                ret
                            else
                                :($name = $glob_name)
                            end
                        end

                    _ => throw("unsupported closures")
                end
            end
        Expr(hd, tl...) => Expr(hd, map(rec, tl)...)
        a => a
    end
end

function closure_conv(block)
    defs = Expr[]
    push!(defs, mk_closure_static(scoping(block), defs))
    Expr(:block, defs...)
end

macro closure_conv(block)
    closure_conv(block) |> esc
end

```

To support mutable free variables, we should capture the structure  
where free variables are stored, it’s a bit internal.

To support self/mutual recursions, we can make `Closure` mutable,  
and make the free variables wrapped in `Ref`:

```julia
function ...
    function g(x)
        do_some(g, x)
    end
end

```

Above codes can be statically transformed to

```julia
function glob_g((refg,), x)
    g = refg.x
    do_some(g, x)
end

function ...
    let refg = Ref{Closure}(),
        frees = (refg, ),
        closure = Closure{glob_x, typeof(frees)}(frees)
        refg.x = closure
        closure
    end
end

```

Until now, we don’t have such a `scoping` function in the community,  
and it’s somewhat a little heavy assignment.

We’re now looking for people to implement this together.

## Generating Functions From Typeable Data In Runtime

In this sub-section, we’ll introduce a method equivalently powerful as `eval`  
but without a world age problem.

Our main goal is to allow closures in generated functions,  
which is performing runtime code generation.

Above techniques(closure conversions) are purely static, thus useless to our goal.

However, we propose a type to achieve generating non-closure functions  
in runtime:

```julia
struct RuntimeFn{Args, Kwargs, Body} end

```

where both `Args` and `Kwargs` are typeable representations of  
the ASTs that represent arguments,  
and `Body` is a typeable representation of Julia AST.

At here, we’ll use the `TypeLevel` mentioned above to represent `Body`.

Then comes one of key ideas:

```julia
using Parameters
@generated function (::RuntimeFn{Args, Kwargs, Body})(args...; kwargs...) where {Args, Kwargs, Body}
    args_ = interpret(Args)
    kwargs_ = interpret(Kwargs)
    body = interpret(Body)
    quote
        $args_ = args
        @unpack $kwargs_ = kwargs
        $body
    end
end

args = expr2typelevel(:(x, y))
kwargs = expr2typelevel(:())
body = expr2typelevel(:(x + y))
fn = RuntimeFn{args, kwargs, body}()

fn(1, 2) # => 3

```

Here comes the runtime generations of functions!

From now on, no need for `eval` and `invokelatest`!

## Closure Conversions For Generated Functions

Now things have got clarified! Since we can already generate non-closure functions  
from arbitrary typeable data in runtime, we can then perform closure conversions  
based on runtime generated non-closure functions, instead of generating static top  
level(global scope) functions.

Following implementation will support closures in generated functions,  
when no default arguments used.

```julia
function closure_conv_staged(expr)
    rec = closure_conv_staged
    @match expr begin
        # main logic
        Expr(:scope, _, frees, _, inner_expr) =>
            let closure_arg = Expr(:tuple, frees...),
                name = "",
                args = Symbol[]
                @match inner_expr begin
                    Expr(:function, :($name($(args...), )), body) ||
                    # (a, b, c, ...) -> body / function (a, b, c, ...) body end
                    Expr(:-> || :function, Expr(:tuple, args...), body) ||
                    # a -> body
                    Expr(:-> || :function, a::Symbol, body) && Do(args=[a]) =>
                        let (args, kwargs) = split_args_kwargs(args),
                            body = rec(body),
                            kwargs = map(x -> x.args[1], kwargs)
                            Kwargs = expr2typelevel(Expr(:tuple, kwargs...))
                            Body = expr2typelevel(body)
                            if isempty(frees)
                                Args = expr2typelevel(Expr(:tuple, args...))
                                RuntimeFn{Args, Kwargs, Body}()
                            else
                                Args = expr2typelevel(Expr(:tuple, closure_arg, args...))
                                non_closure_fn = RuntimeFn{Args, Kwargs, Body}()
                                ret = :(let frees = $closure_arg
                                    $Closure{$non_closure_fn, typeof(frees)}(frees)
                                end)
                                if name == "" # anonymous function
                                    ret
                                else
                                    :($name = $ret)
                                end
                            end
                        end
                    _ => throw("unsupported closures")
                end
            end
        Expr(hd, tl...) => Expr(hd, map(rec, tl)...)
        a => a
    end
end

```

The use is simple:

```julia
function gg(x)
    closure_conv_staged(scoping(x))
end

@generated function f(x)
    quote
        () -> x + 1
    end |> gg
end

```

Currently, what we only lack of is the implementation of `scoping`, and  
making it is expected to cost a few days. However, for prototyping, we  
can simply the cases, by peforming explicit capturing.

In our prototype, we use following notations to express a closure function,  
which looks pretty similar to those in C++:

```julia
# x, y is free variables
[x, y](a) -> x*(a + y)
[](a) -> a

```

In our test cases, you can find out codes that look like

```julia
@generated function f(x)
    quote
        [x](a ) -> x + a
    end |> gg
end
@test f(1)(2) == 3

```

---

<div class="post-metadata">

**Author:** ![yuyichao](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yuyichao/32/20_2.png) [@yuyichao](https://discourse.julialang.org/u/yuyichao)\
**Post date:** [August 12, 2019, 12:25pm UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/2 "2019-08-12T12:25:54Z")

</div>

The support of closures in generated function part is reasonable since it’s an implemention limitation anyway and to hash the type into the type is exactly what Jameson mentioned a long time ago (a year?) to make it compatible with the runtime.

---

<div class="post-metadata">

**Author:** ![thautwarm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/thautwarm/32/37760_2.png) [@thautwarm](https://discourse.julialang.org/u/thautwarm)\
**Post date:** [August 12, 2019, 12:46pm UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/3 "2019-08-12T12:46:29Z")

</div>

Well, I pondered for quite a while and just don’t know how to response…  
At least I think my prototype does help to solving my friend’s issue, which is enough to me. No matter if the method is brand-new, I enjoyed the process of working it out.  
I think there should be somewhere for you core devs to post the more advanced problems of various topics. It’s always difficult to find out your so many previous conclusions in the internet if you don’t assembly them.

---

<div class="post-metadata">

**Author:** ![cscherrer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cscherrer/32/7631_2.png) [@cscherrer](https://discourse.julialang.org/u/cscherrer)\
**Post date:** [August 12, 2019, 3:11pm UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/4 "2019-08-12T15:11:08Z")

</div>

@thautwarm is being kind in using “we” a lot in her description of the solution, so I think some context is helpful.

I’ve struggled with the right way to get around `eval`/`invokelatest` since very early in my work on `Soss` (first commit December 2017). At JuliaCon 2019 I learned the several others were facing this same problem, as discussed [here](https://discourse.julialang.org/t/codegen-woes/27098/8).

@mohamed82008 had previously suggested to me to encode my entire model in the type in order to use generated functions, but I couldn’t quite figure out how to get it to work. And @jpfairbanks had a similar suggestion for encoding algebraic manipulations as types. [We had been joking around and I thought he was trolling me, but now it doesn’t seem so infeasible].

Anyway, even if generated functions had supported closures from the beginning, it wouldn’t have been clear to me how to take advantage of this. So the detailed write-up above is really helpful. Really, my part in GG.jl so far has been to provide an interesting problem to work on 🙂

---

<div class="post-metadata">

**Author:** ![thautwarm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/thautwarm/32/37760_2.png) [@thautwarm](https://discourse.julialang.org/u/thautwarm)\
**Post date:** [August 12, 2019, 3:44pm UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/5 "2019-08-12T15:44:59Z")

</div>

Thanks Chad. The discussion about getting around `eval/invokelatest` is really meaningful. We should make clear what is necessary to do to achieve the goal.

Glad to know several kind people have suggested you to encode you model in the type. Fortunately, we now can just encode the julia AST in the type(via `expr2typelevel` in GG.jl, and I’ll explain this to you later), thus the existing code(exactly how your Model gets transformed to julia AST) doesn’t need to change.

> Really, my part in GG.jl so far has been to provide an interesting problem to work on

No worries… The 2 core ideas of GG.jl are not as complicated as your statistic models…

---

<div class="post-metadata">

**Author:** ![Roger-luo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/roger-luo/32/3399_2.png) [@Roger-luo](https://discourse.julialang.org/u/Roger-luo)\
**Post date:** [August 12, 2019, 6:04pm UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/6 "2019-08-12T18:04:45Z")

</div>

I think we are already using this trick in Zygote in order to create closure for SSA, since you can’t have closure there. FYI: [https://github.com/FluxML/Zygote.jl/blob/master/src/compiler/interface2.jl](https://github.com/FluxML/Zygote.jl/blob/master/src/compiler/interface2.jl)

Or you can check my blog post:  
[http://blog.rogerluo.me/2019/07/27/yassad/](http://blog.rogerluo.me/2019/07/27/yassad/)

---

<div class="post-metadata">

**Author:** ![tkf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tkf/32/17635_2.png) [@tkf](https://discourse.julialang.org/u/tkf)\
**Post date:** [August 13, 2019, 1:07am UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/7 "2019-08-13T01:07:25Z")

</div>

Wow, this is great! It could also be a playground for possible Julia features/extensions, not just “workaround.” For example, I’ve been wanting something like

```julia
function f(x)
    const y = compute(x)
    a -> y + a
end

```

to communicate with compiler that captured variable won’t change so that it does not have to box it (I know FastClosures.jl but I think “local const” will be more clear about the intent and avoid [issue like this](https://github.com/c42f/FastClosures.jl/issues/11)). I suppose it’s possible to do this in GG.jl?

> [@thautwarm](#):
>
> I used to introduce this to  
> the fans of DrRacket, they immediately responsed, “generating functions is trivial, exactly the most common sight in LISP idioms”.

A bit tangent, but since you are talking about Racket… There is [Hackett](https://github.com/lexi-lambda/hackett) that combines Haskell’s type system and Racket’s macro system. IIRC, this talk by Alexis King explains the idea and difficulty of the interaction of type inference and metaprogramming. It reminded me a lot of Julia’s `@generated` function.

[![](https://global.discourse-cdn.com/julialang/original/3X/c/0/c086cfd6c52c6947d8aff4f1846b004725187e96.jpeg ""Hackett: a metaprogrammable Haskell" by Alexis King") ](https://www.youtube.com/watch?v=5QQdI3P7MdY)

---

<div class="post-metadata">

**Author:** ![c42f](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/c42f/32/52842_2.png) [@c42f](https://discourse.julialang.org/u/c42f)\
**Post date:** [August 13, 2019, 3:59am UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/8 "2019-08-13T03:59:43Z")

</div>

> [@thautwarm](#):
>
> Currently, what we only lack of is the implementation of `scoping` , and  
> making it is expected to cost a few days. However, for prototyping, we  
> can simply the cases, by peforming explicit capturing.

This is part of the task of porting the lowering code from femtolisp into julia which I have started over at [https://github.com/JuliaLang/julia/pull/32201](https://github.com/JuliaLang/julia/pull/32201). It’s a big job to do it all, but a working approximation may not be too hard. I had to do some of the same kind of variable analysis in `FastClosures`, but there are some inherent limitations to that approach as @tkf points out.

If anyone would like to collaborate on converting lowering into julia that would be great. One approach would be to take that PR out of Base and into a package for easier collaboration. Then aim to get some of the tooling committed to Base in the short term so we can more easily call into the flisp code for testing.

In terms of the flisp code, I think variable analysis is related to passes 2,3 in julia-syntax.scm, though I haven’t read this code yet. Pass 4 may also be relevant:

- [pass 2: identify and rename local vars](https://github.com/JuliaLang/julia/blob/6527de0c0fe8120531df9b439fd89e9480e3a652/src/julia-syntax.scm#L2374)
- [pass 3: analyze variables](https://github.com/JuliaLang/julia/blob/6527de0c0fe8120531df9b439fd89e9480e3a652/src/julia-syntax.scm#L2579)
- [pass 4: closure conversion](https://github.com/JuliaLang/julia/blob/6527de0c0fe8120531df9b439fd89e9480e3a652/src/julia-syntax.scm#L2721)

As part of porting lowering, it would be interesting to know which APIs people would like to introspect the lowering passes. For one thing, I think it would be useful to have `@code_desugared` which would remove syntactic sugar without converting code to linearized IR. The desugared code is still hierarchical and can be easier to read than the fully linearized code. It would also be handy to have an API which can somehow tell you about the use of variables at different points in the code (which is what you want for this project).

---

<div class="post-metadata">

**Author:** ![thautwarm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/thautwarm/32/37760_2.png) [@thautwarm](https://discourse.julialang.org/u/thautwarm)\
**Post date:** [August 13, 2019, 8:56am UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/9 "2019-08-13T08:56:33Z")

</div>

Thanks for your kind words. In fact what you want is partly implemented in GG.jl.  
The `mk_function` api can make a function in runtime, and you can insert julia objects to be the default values of function arguments(but now only works for keyword arguments).

```julia
y = compute(x)
f = mk_function(:(a, ), :(y = $y, ), :(y + a))

```

When you invoke `f` without keyword arguments, the generated code is exactly:

```julia
quote
   let (a, ) = args, # not sure but feel like this line can be optimized
        y = $y,
        y + a
   end
end

```

---

<div class="post-metadata">

**Author:** ![thautwarm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/thautwarm/32/37760_2.png) [@thautwarm](https://discourse.julialang.org/u/thautwarm)\
**Post date:** [August 13, 2019, 9:06am UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/10 "2019-08-13T09:06:27Z")

</div>

Your works should be greatly appreciated, and I need time to digest what you mentioned.

In fact I have some experience with the scoping analysis in programming languages like Python, several ML-dialects. I do have some tools/ideas to handle this sort of tasks, but after I dipped into your PR and the referenced flisp files, I found the stuffs really complicated…

I need more time to digest it.

---

<div class="post-metadata">

**Author:** ![freemint](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/freemint/32/20013_2.png) [@freemint](https://discourse.julialang.org/u/freemint)\
**Post date:** [August 13, 2019, 10:45am UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/11 "2019-08-13T10:45:09Z")

</div>

> [@thautwarm](#):
>
> What we’re bringing about today are
> 
> 1. the capability of creating functions in runtime without `eval` or introducing a world age problem.
> 2. the capability of defining closures in generated functions.

How does it compare to [GitHub - yuyichao/FunctionWrappers.jl](https://github.com/yuyichao/FunctionWrappers.jl) in terms of performance and types of functions allowed?

---

<div class="post-metadata">

**Author:** ![thautwarm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/thautwarm/32/37760_2.png) [@thautwarm](https://discourse.julialang.org/u/thautwarm)\
**Post date:** [August 13, 2019, 11:25am UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/12 "2019-08-13T11:25:14Z")

</div>

I don’t think Yuyichao’s FunctionWrapper.jl has much overlap with GG.jl.  
For what I’m concerned, his package provides the structutal function types like what in statically typed languages, which is tramendously essential for it provides the capability of polymorphisms among function types:

```julia
map(f::FunctionWrapper{R, Tuple{T}}, c::Vector{T}) :: Vector{R} = ...
# which ends the awkward scene of `map(f, [])`

```

However I’ve heard that it could wrap the function in other world age, so far working well along with eval.

While GG.jl can remove the use of eval and make functions via runtime Julia Expe data(ASTs).

Also, GG.jl has no efficiency cost if the baseline is a native generated function with same semantics defined in global scope/top level of a module:

```julia
julia> using GG; using BenchmarkTools
julia> struct D end
julia> @generated (::D)(x, y) = :(x + y)
julia> fd = D();
julia> fg = mk_function(:(x, y), :(), :(x + y));
julia> fd(1, 2); fg(1, 2); # compile for the first time
julia> @btime fd(1, 2)
  13.203 ns (0 allocations: 0 bytes)
3
julia> @btime fg(1, 2)
  14.003 ns (0 allocations: 0 bytes)
3
julia> @code_llvm fd(1, 2)
#=
; @ REPL[4]:1 within `D'
define i64 @julia_D_12305(i64, i64) {
top:
; ┌ @ REPL[4]:1 within `macro expansion'
; │┌ @ int.jl:53 within `+'
    %2 = add i64 %1, %0
; │└
   ret i64 %2
; └
}
=#
julia> @code_llvm fg(1, 2)
#=
; @ /home/redbq/github2/GG/src/closure_conv.jl:77 within `RuntimeFn'
define i64 @julia_RuntimeFn_12309(i64, i64) {
top:
; ┌ @ /home/redbq/github2/GG/src/closure_conv.jl:81 within `macro expansion'
; │┌ @ int.jl:53 within `+'
    %2 = add i64 %1, %0
; │└
   ret i64 %2
; └
}
=#

```

**EDIT** : Due to my implementation limitation(you know now GG.jl is a proof of concepts), some forms of function definition(annotations or specification of return type) are not allowed.

---

<div class="post-metadata">

**Author:** ![cscherrer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cscherrer/32/7631_2.png) [@cscherrer](https://discourse.julialang.org/u/cscherrer)\
**Post date:** [August 13, 2019, 4:54pm UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/13 "2019-08-13T16:54:25Z")

</div>

> [@thautwarm](#):
>
> GG.jl has no efficiency cost if the baseline is a native generated function with same semantics defined in global scope/top level of a module

Even at global scope you can make this approach very fast with `const`:

```julia
julia> f = mk_function(:(x, y), :(), :(x + y))
RuntimeFn{GG.TLSExp{GG.TLVal{Expr},GG.TLCons{GG.TLVal{:tuple},GG.TLCons{GG.TLVal{:x},GG.TLCons{GG.TLVal{:y},GG.TLNil}}}},GG.TLSExp{GG.TLVal{Expr},GG.TLCons{GG.TLVal{:tuple},GG.TLNil}},GG.TLSExp{GG.TLVal{Expr},GG.TLCons{GG.TLVal{:call},GG.TLCons{GG.TLVal{:+},GG.TLCons{GG.TLVal{:x},GG.TLCons{GG.TLVal{:y},GG.TLNil}}}}}}()

julia> const g = mk_function(:(x, y), :(), :(x + y))
RuntimeFn{GG.TLSExp{GG.TLVal{Expr},GG.TLCons{GG.TLVal{:tuple},GG.TLCons{GG.TLVal{:x},GG.TLCons{GG.TLVal{:y},GG.TLNil}}}},GG.TLSExp{GG.TLVal{Expr},GG.TLCons{GG.TLVal{:tuple},GG.TLNil}},GG.TLSExp{GG.TLVal{Expr},GG.TLCons{GG.TLVal{:call},GG.TLCons{GG.TLVal{:+},GG.TLCons{GG.TLVal{:x},GG.TLCons{GG.TLVal{:y},GG.TLNil}}}}}}()

julia> @btime f(1,2)
  235.405 ns (1 allocation: 32 bytes)
3

julia> @btime g(1,2)
  0.017 ns (0 allocations: 0 bytes)
3

julia> @btime 1+2
  0.017 ns (0 allocations: 0 bytes)
3

```

---

<div class="post-metadata">

**Author:** ![thautwarm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/thautwarm/32/37760_2.png) [@thautwarm](https://discourse.julialang.org/u/thautwarm)\
**Post date:** [August 13, 2019, 5:19pm UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/14 "2019-08-13T17:19:01Z")

</div>

Wow, I don’t know this before! Makes a lot of sense to me!

**EDIT** :  
After pondering for a while I think it might not be very useful. I think it’s caused by inlining, but when  
things get really dynamic, inlining might not work.

Not sure, can any one answer this?

---

<div class="post-metadata">

**Author:** ![cscherrer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cscherrer/32/7631_2.png) [@cscherrer](https://discourse.julialang.org/u/cscherrer)\
**Post date:** [August 13, 2019, 5:23pm UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/15 "2019-08-13T17:23:57Z")

</div>

Right, in previous tests I had thought it’s a bit slow, but I think this was just an issue with global scope. `const` removes this, but you mostly wouldn’t really use it this way. I think a more realistic benchmark builds code and calls it in the same world age. Hopefully using it in Soss will also give us some more insight into corner cases.

---

<div class="post-metadata">

**Author:** ![thautwarm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/thautwarm/32/37760_2.png) [@thautwarm](https://discourse.julialang.org/u/thautwarm)\
**Post date:** [August 13, 2019, 5:28pm UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/16 "2019-08-13T17:28:41Z")

</div>

@cscherrer

Haha, I figured it out, and it seems that GG.jl is soooo fast:

```julia
julia> seq = 1:1000;

julia> apply1 = mk_function(:(x, ), :(), :(2x + 1))
RuntimeFn{GG.TLSExp{GG.TLVal{Expr},GG.TLCons{GG.TLVal{:tuple},GG.TLCons{GG.TLVal{:x},GG.TLNil}}},GG.TLSExp{GG.TLVal{Expr},GG.TLCons{GG.TLVal{:tuple},GG.TLNil}},GG.TLSExp{GG.TLVal{Expr},GG.TLCons{GG.TLVal{:call},GG.TLCons{GG.TLVal{:+},GG.TLCons{GG.TLSExp{GG.TLVal{Expr},GG.TLCons{GG.TLVal{:call},GG.TLCons{GG.TLVal{:*},GG.TLCons{GG.TLVal{2},GG.TLCons{GG.TLVal{:x},GG.TLNil}}}}},GG.TLCons{GG.TLVal{1},GG.TLNil}}}}}}()

julia> apply2 = x -> 2x + 1
#8 (generic function with 1 method)

julia> @btime map(apply1, seq);
  635.250 ns (1 allocation: 7.94 KiB)

julia> @btime map(apply2, seq);
  724.568 ns (4 allocations: 8.03 KiB)

julia> @btime map(apply2, seq);
  704.625 ns (4 allocations: 8.03 KiB)

julia> @btime map(apply1, seq);
  674.888 ns (1 allocation: 7.94 KiB)

```

**EDIT** : named functions or type annotated functions are still slower than `mk_function`. Weird.

---

<div class="post-metadata">

**Author:** ![tkf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tkf/32/17635_2.png) [@tkf](https://discourse.julialang.org/u/tkf)\
**Post date:** [August 13, 2019, 9:34pm UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/17 "2019-08-13T21:34:53Z")

</div>

Interesting. Since you move `eval` up to type-domain, `eval`ing a function is equivalent to making a closure? This is neat.

---

<div class="post-metadata">

**Author:** ![YingboMa](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yingboma/32/2181_2.png) [@YingboMa](https://discourse.julialang.org/u/YingboMa)\
**Post date:** [September 4, 2019, 9:18pm UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/18 "2019-09-04T21:18:41Z")

</div>

BTW, you don’t have to use `const` to benchmark it. You could use `$` to interpolate variables that are in the global scope.

```julia
julia> using BenchmarkTools

julia> f = mk_function(:(x, y), :(), :(x + y))
RuntimeFn{GG.TLSExp{GG.TLVal{Expr},GG.TLCons{GG.TLVal{:tuple},GG.TLCons{GG.TLVal{:x},GG.TLCons{GG.TLVal{:y},GG.TLNil}}}},GG.TLSExp{GG.TLVal{Expr},GG.TLCons{GG.TLVal{:tuple},GG.TLNil}},GG.TLSExp{GG.TLVal{Expr},GG.TLCons{GG.TLVal{:call},GG.TLCons{GG.TLVal{:+},GG.TLCons{GG.TLVal{:x},GG.TLCons{GG.TLVal{:y},GG.TLNil}}}}}}()

julia> @btime f(1, 2)
  17.372 ns (0 allocations: 0 bytes)
3

julia> @btime $f(1, 2)
  0.026 ns (0 allocations: 0 bytes)
3

```

Also, be aware that sub-nano timing is almost certainly a fluke because [one CPU cycle](https://www.d.umn.edu/~gshute/arch/performance-equation.xhtml) is about 0.25 ns already. You could try to use the `Ref` trick to correctly benchmark it.

```julia
julia> @btime $f(Ref(1)[], 2)
  1.277 ns (0 allocations: 0 bytes)
3

julia> @btime Ref(1)[] + 2
  1.278 ns (0 allocations: 0 bytes)
3

```

---

<div class="post-metadata">

**Author:** ![cscherrer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cscherrer/32/7631_2.png) [@cscherrer](https://discourse.julialang.org/u/cscherrer)\
**Post date:** [September 4, 2019, 9:30pm UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/19 "2019-09-04T21:30:08Z")

</div>

> [@YingboMa](#):
>
> BTW, you don’t have to use `const` to benchmark it. You could use `$` to interpolate variables that are in the global scope.

Right. This has been confusing to me, determining the best way to measure things in a way that will be representative of a given use case. I was concerned about speed when calling from global scope, in case the function is used in that way.

> [@YingboMa](#):
>
> You could try to use the `Ref` trick to correctly benchmark it.

I don’t know this trick, where can I read more?

---

<div class="post-metadata">

**Author:** ![YingboMa](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yingboma/32/2181_2.png) [@YingboMa](https://discourse.julialang.org/u/YingboMa)\
**Post date:** [September 4, 2019, 9:52pm UTC](https://discourse.julialang.org/t/a-method-to-remove-the-use-of-runtime-eval-and-invokelatest-as-well-as-support-closures-in-generated-functions-come-with-an-implemented-proptype/27450/20 "2019-09-04T21:52:13Z")

</div>

> [@cscherrer](#):
>
> I don’t know this trick, where can I read more?

I am not sure if it is documented anywhere, but you could do

```julia
julia> @btime $f(x, y) setup=(x=rand(Int); y=rand(Int))
  1.278 ns (0 allocations: 0 bytes)
5287131514655690321

```

, too.

The manual of BenchmarkTools.jl is [here](https://github.com/JuliaCI/BenchmarkTools.jl/blob/master/doc/manual.md).
