# Best practices for implicit function parameters

**URL:** <https://discourse.julialang.org/t/best-practices-for-implicit-function-parameters/39699>\
**Category:** General Usage\
**Created:** [May 18, 2020, 2:45pm UTC](https://discourse.julialang.org/t/best-practices-for-implicit-function-parameters/39699 "2020-05-18T14:45:30Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![hessammehr](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hessammehr/32/3847_2.png) [@hessammehr](https://discourse.julialang.org/u/hessammehr)\
**Post date:** [May 18, 2020, 2:45pm UTC](https://discourse.julialang.org/t/best-practices-for-implicit-function-parameters/39699/1 "2020-05-18T14:45:30Z")

</div>

Often packages require passing callbacks with a fixed signature `f(x)` where my implementation of `f` will need to refer to an auxiliary variable in the surrounding context called `a`. Given how the use of global variables is generally discouraged in Julia, I set out to try a few different ways of achieving this and was surprised by the differences in their performance. I want share my findings with everyone and ask what you find a good trade-off between performance and interactivity (being able to redefine `a` in the REPL, for instance, is very useful).

In the code below we would like a function `b(x)` that does some computation based on the value of `x` as well as auxiliary data stored in `a`.

```julia
# auxiliary data
a = rand(10)

# a is dynamic
b1(x) = sum(x) + sum(a)

# a is static
b2 = let a = a
    x -> sum(x) + sum(a)
end

# a is static
makeb(a) = x -> sum(x) + sum(a)
b3 = makeb(a)

# a is static
module B
    const a = Main.a
    b(x) = sum(x) + sum(a)
end
b4 = B.b

# a is dynamic
b5(x) = sum(x) + sum(a::Array{Float64, 1})

# a is dynamic
b6(x, a) = sum(x) + sum(a)
b6(x) = b6(x, a)

# a is static
const c = a
b7(x) = sum(x) + sum(c)

# not ideal since you can't redefine this struct
# to add more context variables
struct MyContext
    a::Array{Float64, 1}
end

(c::MyContext)(x) = sum(x) + sum(c.a)
b8 = MyContext(a)

```

Timings for small arrays:

```julia
using BenchmarkTools

@benchmark b1($(rand(10)))
BenchmarkTools.Trial: 
  memory estimate: 48 bytes
  allocs estimate: 3
  --------------
  minimum time: 46.747 ns (0.00% GC)
  median time: 49.536 ns (0.00% GC)
  mean time: 51.960 ns (3.00% GC)
  maximum time: 1.668 μs (94.32% GC)
  --------------
  samples: 10000
  evals/sample: 988

@benchmark b2($(rand(10)))
BenchmarkTools.Trial: 
  memory estimate: 16 bytes
  allocs estimate: 1
  --------------
  minimum time: 23.427 ns (0.00% GC)
  median time: 24.795 ns (0.00% GC)
  mean time: 26.231 ns (1.83% GC)
  maximum time: 1.659 μs (97.09% GC)
  --------------
  samples: 10000
  evals/sample: 996

@benchmark b3($(rand(10)))
BenchmarkTools.Trial: 
  memory estimate: 16 bytes
  allocs estimate: 1
  --------------
  minimum time: 23.428 ns (0.00% GC)
  median time: 25.027 ns (0.00% GC)
  mean time: 26.318 ns (1.78% GC)
  maximum time: 1.616 μs (96.91% GC)
  --------------
  samples: 10000
  evals/sample: 996

@benchmark b4($(rand(10)))
BenchmarkTools.Trial: 
  memory estimate: 16 bytes
  allocs estimate: 1
  --------------
  minimum time: 22.965 ns (0.00% GC)
  median time: 23.428 ns (0.00% GC)
  mean time: 24.691 ns (1.91% GC)
  maximum time: 1.623 μs (97.06% GC)
  --------------
  samples: 10000
  evals/sample: 996

@benchmark b5($(rand(10)))
BenchmarkTools.Trial: 
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 9.206 ns (0.00% GC)
  median time: 9.327 ns (0.00% GC)
  mean time: 9.519 ns (0.00% GC)
  maximum time: 18.342 ns (0.00% GC)
  --------------
  samples: 10000
  evals/sample: 999

@benchmark b6($(rand(10)))
BenchmarkTools.Trial: 
  memory estimate: 16 bytes
  allocs estimate: 1
  --------------
  minimum time: 23.930 ns (0.00% GC)
  median time: 24.393 ns (0.00% GC)
  mean time: 25.667 ns (1.84% GC)
  maximum time: 1.630 μs (97.02% GC)
  --------------
  samples: 10000
  evals/sample: 996

@benchmark b7($(rand(10)))
BenchmarkTools.Trial: 
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 9.136 ns (0.00% GC)
  median time: 9.196 ns (0.00% GC)
  mean time: 9.195 ns (0.00% GC)
  maximum time: 14.722 ns (0.00% GC)
  --------------
  samples: 10000
  evals/sample: 999

@benchmark b8($(rand(10)))
BenchmarkTools.Trial: 
  memory estimate: 16 bytes
  allocs estimate: 1
  --------------
  minimum time: 24.050 ns (0.00% GC)
  median time: 24.785 ns (0.00% GC)
  mean time: 26.049 ns (1.82% GC)
  maximum time: 1.636 μs (97.25% GC)
  --------------
  samples: 10000
  evals/sample: 996

```

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [May 18, 2020, 2:55pm UTC](https://discourse.julialang.org/t/best-practices-for-implicit-function-parameters/39699/2 "2020-05-18T14:55:04Z")

</div>

> [@hessammehr](#):
>
> Often packages require passing callbacks with a fixed signature `f(x)` where my implementation of `f` will need to refer to an auxiliary variable in the surrounding context called `a` .

Just use lexical scoping to capture the needed variables. e.g. if you have a function `g(x,a)`, pass it as `x -> g(x,a)`.

However, you need to benchmark this using functions, i.e. not in global scope. For example:

```julia
julia> X = rand(1000);

julia> f1(X) = sum(x -> x + 1, X);

julia> f2(X,a) = sum(x -> x + a, X);

julia> @btime f1($X);
  85.497 ns (0 allocations: 0 bytes)

julia> @btime f2($X, 1);
  88.206 ns (0 allocations: 0 bytes)

```
