# Some notes on performance of callable structs

**URL:** <https://discourse.julialang.org/t/some-notes-on-performance-of-callable-structs/108042>\
**Category:** Performance\
**Created:** [December 25, 2023, 5:32pm UTC](https://discourse.julialang.org/t/some-notes-on-performance-of-callable-structs/108042 "2023-12-25T17:32:14Z")\
**Posts on this page:** 1\
**Showing post:** 5

<div class="post-metadata">

**Author:** ![arik](https://avatars.discourse-cdn.com/v4/letter/a/c4cdca/32.png) [@arik](https://discourse.julialang.org/u/arik)\
**Post date:** [December 29, 2023, 2:17pm UTC](https://discourse.julialang.org/t/some-notes-on-performance-of-callable-structs/108042/5 "2023-12-29T14:17:01Z")

</div>

Maybe I should be a bit more concrete about the problem I was trying to solve. Julia has pretty good support for function pipelines or higher order functions that take other functions as arguments, like

```julia
arr2 = map(x -> foo(1, x), arr1)

```

or

```julia
# from my implementation of a circular buffer for an exercism exercise
Base.getindex(cb::CircularBuffer, i) =
   # wraps the index over the internal array
    checkedindex(cb,i) |>
    x -> getindex(cb.data, x)

```

These are already pretty nice, but there are a lot of attempts out there to make them even more ergonomic and concise, like Underscores.jl or the aforementioned PartialFunctions.jl, with which these could be rewritten as

```julia
arr2 = map(foo $ 1, arr1)

```

and

```julia
# from my implementation of a circular buffer for an exercism exercise
Base.getindex(cb::CircularBuffer, i) =
    checkedindex(cb,i) |>
    getindex $ cb.data

```

I personally really love this style of generating small new functions, and I am certain I am not alone in this given the many attempts to retrofit such features onto julia (afaik there has been a lengthy discussion to essentially make the `__` syntax from Underscores.jl a native language feature). It is quite amazing that julia allows you to just ‘add new syntax’ like this, and in many cases this can even happen completely without loss of performance, due to the way functions are integrated in the type system.

However, in cases where a constant literal is partially applied to a function, like in my `map(x -> foo(1, x), arr1)` example, the native arrow syntax will create bytecode where the 1 is inlined into the code (as mentioned by @Benny), while for the `map(foo $ 1, arr1)` this does not happen, leading to slightly reduced performance in some cases. I spend a bit of time thinking about how one could rewrite/improve the definition of the `$` function to remove these last drawbacks of partial application compared to the native `->` syntax, but so far I think there is none. By stuffing the 1 in a struct that acts as a function we ‘hide’ the fact that it is really just a literal constant from the compiler.

> [@Benny](#):
>
> This wouldn’t be better with an anonymous function. In your example, `x -> expensiveFirst(1, x)` was only efficient because it inlines a constant literal `1`.

Yes, but that is the whole point. With a native anonymous function the compiler ‘sees’ which values are literals and which are variables and from which scope they come. By using these function structs every value is treated the same, no matter where they come from. That it is in fact better can be seen from the benchmark I gave (you can replace the `Fix1(expensiveFirst,1)` with `expensiveFirst $ 1`, they are functionally the same).

@nsaiko :I skimmed over the linked thread. The topics are related I think (constant propagation/folding. BTW thanks to your comment I realized I should read up these terms and understand the difference) but as I understood it it’s author did not get the answer he was looking for, which would reinforce my statement that there exists no solution.  
I read your explanations [here](https://discourse.julialang.org/t/tricks-to-compile-away-step-in-calculation/105760/6#a-simple-example-2), and I understand the difference between `closed_function_without_run_time_dispatch` and `closed_function_with_run_time_dispatch`, it is similar in concept to `Fix1` vs `ValueFix1` ,The reason this does not help me in my case is that when I define the function `$(f, x)` I can assume that the nature of `f` is known, but whether `x` is a compile time known literal ( like in `map(foo $ 1, arr1)`) or a variable that can vary at runtime (like in `checkedindex(cb,i) |> getindex $ cb.data`) is not, so I must assume it is a variable and treat it accordingly. I have to admit I have never used the `Base.@assume_effects` macro. Not sure if that may help out here.

I also want to emphasize that this thread is absolutely not a complaint, I would agree that the way the type system allows you to ‘talk’ to the compiler and make it evaluate stuff at compile time is absolutely busted, and way ahead of any other programming language I have ever worked with (I have heard Zig has some interesting comptime mechanic?). I just happened to run across this problem of extending the syntax without increasing the runtime cost which I could not solve and decided to share it.

---

_[View the full topic](https://discourse.julialang.org/t/some-notes-on-performance-of-callable-structs/108042)._
