# How to get the free variables of a function?

**URL:** <https://discourse.julialang.org/t/how-to-get-the-free-variables-of-a-function/89892>\
**Category:** General Usage\
**Created:** [November 7, 2022, 4:20pm UTC](https://discourse.julialang.org/t/how-to-get-the-free-variables-of-a-function/89892 "2022-11-07T16:20:15Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![jt1](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jt1/32/38331_2.png) [@jt1](https://discourse.julialang.org/u/jt1)\
**Post date:** [November 7, 2022, 4:20pm UTC](https://discourse.julialang.org/t/how-to-get-the-free-variables-of-a-function/89892/1 "2022-11-07T16:20:15Z")

</div>

Is there a way to get thefree variables of a function?  
For example:

```julia
myfunc(a) = a+some_func(b,a)
free_variables(myfunc) # returns (:b,) or (:b, :some_func) is also fine

```

A follow-up question (that I will likely have to ask somewhere else): Is there a lint in vscode that marks these variables as they could prevent some bugs?

---

<div class="post-metadata">

**Author:** ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)\
**Post date:** [November 7, 2022, 6:39pm UTC](https://discourse.julialang.org/t/how-to-get-the-free-variables-of-a-function/89892/2 "2022-11-07T18:39:50Z")

</div>

The technical term for this is “free variables”: [Free variables and bound variables - Wikipedia](https://en.wikipedia.org/wiki/Free_variables_and_bound_variables). I don’t know of a utility function that computes that list. Perhaps @jeff.bezanson knows of one.

---

<div class="post-metadata">

**Author:** ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)\
**Post date:** [November 7, 2022, 8:11pm UTC](https://discourse.julialang.org/t/how-to-get-the-free-variables-of-a-function/89892/3 "2022-11-07T20:11:20Z")

</div>

> [@jt1](#):
>
> `nonlocal_vars(a) # returns (:b,) or (:b, :some_func) is also fine`

probably meant `nonlocal_vars(myfunc)`.

In theory, this shoud be derivable from the lowered form of the function.

---

<div class="post-metadata">

**Author:** ![jt1](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jt1/32/38331_2.png) [@jt1](https://discourse.julialang.org/u/jt1)\
**Post date:** [November 9, 2022, 4:52pm UTC](https://discourse.julialang.org/t/how-to-get-the-free-variables-of-a-function/89892/4 "2022-11-09T16:52:34Z")

</div>

I got it to work. I’m sure it is not perfect but it works atleast for my test case. Not sure what I need to do with the `allowed_modules` in the `free_variables` function.

For future reference, I will edit the title to contain free variables and I will fix my typo in the question.

A short explanation: free variables are put in the lowered code as `Main.<>`. But also functions get put in it as `Main.<>`. So we need to filter the valid functions out. (i.e. those that are really defined in Main).

If the goal was to only get free symbols, not free functions. Then `expr.name in defined_in_main` would just become something like `getproperty(Main,expr.name) isa Function`.

```julia
using MacroTools

macro free_variables(ex0...)
    thecall = InteractiveUtils.gen_call_with_extracted_types_and_kwargs( __module__ , :free_variables, ex0)
    quote
        local results = $thecall
        length(results) == 1 ? results[1] : results
    end
end

function free_variables(@nospecialize(f), @nospecialize(t=Tuple))
    allowed_from_main = filter(name->getproperty(Main,name) isa Function,names(Main,imported=true))
    allowed_modules = (Core,Base) #setdiff(values(Base.loaded_modules),:Main)
    defined_in_main = union(map(mod->Set(names(mod,imported=true)),allowed_modules)...,allowed_from_main)

    _codelines = code_lowered(f,t)
    length(_codelines) == 0 && error("Wrong arguments given for function $f")
    codelines = length(_codelines) == 1 ? _codelines[1] : _codelines

    set = Set{Symbol}()
    for line_expr in codelines.code
        MacroTools.postwalk(line_expr) do expr
            expr isa GlobalRef && !(expr.name in defined_in_main) && push!(set,expr.name)
            expr
        end
    end
    (set...,)
end

```

Testing:

```julia
function hypot(x,y)
    x = abs(x+c)
    y = abs(y) + func(x)
    if x > y
        r = y/x
        return x*sqrt(1+r*r) + c + d
    end
    if y == 0
        return zero(x)
    end
    r = x/y
    return y*sqrt(1+r*r)
end

free_vars = free_variables(hypot,(Float64,Float64)) # (:d, :c, :func)
@free_variables hypot(5.,5.) # (:d, :c, :func)

```
