# State of closures, Fix1/Fix2

**URL:** https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997
**Category:** Performance
**Created:** [September 3, 2024, 2:44pm UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997 "2024-09-03T14:44:38Z")
**Posts on this page:** 17
**Page:** 1

<div class="post-metadata">

### Author: ![Deduction42](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/deduction42/32/9206_2.png) [@Deduction42](https://discourse.julialang.org/u/Deduction42)
#### Post date: [September 3, 2024, 2:44pm UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997/1 "2024-09-03T14:44:38Z")

</div>

I’ve been seeing more Base.Fix1 and Base.Fix2 inside code-bases these days, even though I thought most of these performance issues were fixed with v1.0. Apparently they aren’t? In [Performance Tips](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-captured) it looks like we still need to use let-block shenanigans or use FastClosures if we need to avoid accidental type-instabilities (but this is something the community is actively working on improving). The SciML style guide also tells us to avoid closures, recommending Base.Fix1, or Base.Fix2.

Using Fix1/Fix2 is a solution that only applies to the simplest cases; moreover, while this pattern appears to be idiomatic now, the fact that Fix1/Fix2 aren’t exported from base makes it feel like this is a temporary hack that won’t be idiomatic in the future. This is reinforced by the fact that anonymous function arguments (discouraged by SciML style guide) proliferate in the base documentation, like [filter](https://docs.julialang.org/en/v1/base/collections/#Base.filter). Now these examples aren’t technically closures, but they can easily turn into closures.

My question is, which way is the community going on this? Are we going to end up in a place where closures aren’t a problem, or are we going to be more encouraged to use Fix1/Fix2 sorts of objects?

---

<div class="post-metadata">

### Author: ![nilshg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nilshg/32/2283_2.png) [@nilshg](https://discourse.julialang.org/u/nilshg)
#### Post date: [September 3, 2024, 3:23pm UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997/2 "2024-09-03T15:23:39Z")

</div>

> <https://github.com/JuliaLang/julia/pull/54653>
>
> This PR generalises \`Base.Fix1\` and \`Base.Fix2\` to \`Base.Fix{n}\`, to allow fixin…g a single positional argument of a function.
> 
> With this change, the implementation of these is simply
> 
> \`\`\`julia
> const Fix1{F,T} = Fix{1,F,T}
> const Fix2{F,T} = Fix{2,F,T}
> \`\`\`
> 
> Along with the PR I also add a larger suite of unittests for all three of these functions to complement the existing tests for \`Fix1\`/\`Fix2\`.
> 
> \### Context
> 
> There are multiple motivations for this generalization.
> \*\*By creating a more general \`Fix{N}\` type, there is no preferential treatment of certain types of functions:\*\*
> 
> \- (i) No limitation that you can only fix positions 1-2. You can now fix any position \`n\`.
> \- (ii) No asymmetry between 2-argument and n-argument functions. You can now fix an argument for functions with any number of arguments.
> \- (iii) ~~No asymmetry between positional arguments and keyword arguments. You can now fix a keyword argument.~~
> 
> 
> Think of this like if \`Base\` only had \`Vector{T}\` and \`Matrix{T}\`, and you wished to generalise it to \`Array{T,N}\`.
> It is an analogous situation here: \`Fix1\` and \`Fix2\` are now \*aliases\* of \`Fix{N}\`.
> 
> \- \*\*Convenience\*\*:
> - \`Base.Fix1\` and \`Base.Fix2\` are useful shorthands for creating simple anonymous functions without compiling new functions.
> - They are very common throughout the Julia ecosystem as a shorthand for filling arguments:
> - \`Fix1\` https://github.com/search?q=Base.Fix1+language%3Ajulia&type=code
> - \`Fix2\` https://github.com/search?q=Base.Fix2+language%3Ajulia&type=code
> \- \*\*Less Compilation\*\*:
> - Using \`Fix\*\` reduces the need for compilation of repeatedly-used anonymous functions (which can often trigger compilation of new functions).
> \- \*\*Type Stability\*\*:
> - \`Fix\`, like \`Fix1\` and \`Fix2\`, captures variables in a struct, encouraging users to use a functional paradigm for closures, preventing any potential type instabilities from boxed variables within an anonymous function.
> \- \*\*Easier Functional Programming\*\*:
> - Allows for a stronger functional programming paradigm by supporting partial functions with \_any number of arguments\_.
> 
> Note that this refactors \`Fix1\` and \`Fix2\` to be equal to \`Fix{1}\` and \`Fix{2}\` respectively, rather than separate structs. This is backwards compatible.
> 
> Also note that this does not constrain future generalisations of \`Fix{n}\` for multiple arguments. \`Fix{1,F,T}\` is the clear generalisation of \`Fix1{F,T}\`, so this isn't major new syntax choices. But in a future PR you could have, e.g., \`Fix{(n1,n2)}\` for multiple arguments, and it would still be backwards-compatible with this.
> 
> \### Details
> 
> As the names suggest, \`Fix1\` and \`Fix2\`, they can only inject arguments at the first and second index. Furthermore, they are also constrained to work on 2-argument functions exclusively. It seems at various points there had been interest in extending this (see links below) but nobody had gotten around to it so far.
> 
> This implementation of \`Base.Fix\` generalises the form as follows:
> 
> \> \`Fix{n}(f, x)\`
> 
> \> A type representing a partially-applied version of a function \`f\`, with the argument
> \> "x" fixed at argument \`n::Int\` or keyword \`kw::Symbol\`.
> \> In other words, \`Fix{3}(f, x)\` behaves similarly to
> \> \`(y1, y2, y3) -\> f(y1, y2, x, y3)\` for the 4-argument function \`f\`.
> 
> 
> With this more general type, I also rewrite \`Fix1\` and \`Fix2\` in this PR.
> 
> \`\`\`julia
> const Fix1{F,T} = Fix{1,F,T}
> const Fix2{F,T} = Fix{2,F,T}
> \`\`\`
> 
> With \`Fix{n}\`, the code which is executed is roughly as follows:
> 
> \`\`\`julia
> function (f::Fix{N})(args...; kws...) where {N}
> return f.f(args\[begin:begin+(N-2)\]..., f.x, args\[begin+(N-1):end\]...; kws...)
> end
> \`\`\`
> 
> This means that the captured variable \`x\` is inserted at the \`N\`-th position. Keywords are also captured and inserted at the end.
> 
> This adds several unittests, a docstring, as well as type stability checks, for which it seems to succeed.
> 
> I also run the new \`Fix1\` and \`Fix2\` test suites added by this PR on the \`Fix\` version of each struct.
> 
> \### Examples
> 
> \*\*Simple examples:\*\*
> 
> To fix the argument \`f\` with an anonymous function:
> 
> \`\`\`julia
> with\_f = (a, b, c, d, e, g, h, i, j, k) -\> my\_func(a, b, c, d, e, f, g, h, i, j, k)
> \`\`\`
> 
> whereas now it becomes:
> 
> \`\`\`julia
> with\_f = Base.Fix{6}(my\_func, f)
> \`\`\`
> 
> I have some usecases like this in SymbolicRegression.jl. I want to fix a single option in a function, and then repeatedly call that function throughout some loop with different input. \`Fix1\` and \`Fix2\` are not general enough for this as they only allow 2-argument functions.
> 
> A more common use-case I have is to set \`MIME"text/plain"\` in \`print\` for tests, which can now be done as \`Fix\` is no longer limited to 2 arguments:
> 
> \`\`\`julia
> s = sprint(Fix{2}(print, MIME"text/plain"()), my\_object)
> \`\`\`
> 
> without needing to re-compile at each instance.
> 
> 
> \*\*\<details\>\<summary\>In a reduction:\</summary\>\*\*
> 
> Fix1 and Fix2 are useful for short lambda functions like
> 
> \`\`\`julia
> sum(Base.Fix1(\*, 2), \[1, 2, 3, 4, 5\])
> \`\`\`
> 
> to reduce compilation and often improve type stability.
> 
> With this new change, you aren't limited to only 2-arg functions, so you can use things like fma in this context:
> 
> \`\`\`julia
> sum(Base.Fix{2}(Base.Fix{3}(fma, 2.0), 0.5), \[1, 2, 3, 4, 5\])
> \`\`\`
> 
> where this will evaluate as x -\> affine(x, 0.5, 2.0).
> 
> Another example is a mapreduce, where you would typically want to fix the map and reduction in applying:
> 
> \`\`\`julia
> sum(
> Base.Fix{1}(Base.Fix{1}(mapreduce, abs), \*),
> \[\[1, -1\], \[2, -3, 4\], \[5\]\]
> )
> \`\`\`
> 
> \</details\>
> 
> \*\*\<details\>\<summary\>In data processing pipelines:\</summary\>\*\*
> 
> Fix can be used to set any number of arguments, and of course also be chained together repeatedly as there is no restriction on 2-arg functions. It makes functional programming easier than with only Fix1 and Fix2.
> 
> For example, in a processing pipeline:
> 
> \`\`\`julia
> using CSV, DataFrames
> 
> affine(a, b, c) = a .+ b .\* c
> 
> affine\_transform\_df(path) = (
> CSV.read(path, DataFrame)
> |\> dropmissing
> |\> Fix{1}(filter, :id =\> ==(7)) # Use like Fix2
> |\> Fix{2}(Fix{3}(affine, 2.0), 0.5) # Multiple args
> |\> Fix{2}(getproperty, :a)
> )
> 
> affine\_transform\_df("data.csv")
> \`\`\`
> 
> \</details\>
> 
> \*\*\<details\>\<summary\>For dispatching on keyword-fixed functions:\</summary\>\*\*
> 
> Say that I would like to dispatch on
> \`sum\` for some type, if \`dims\` is set to an integer. You can do this as follows:
> 
> \`\`\`julia
> struct MyType
> x::Float64
> end
> 
> function (f::Base.Fix{:dims,typeof(sum),Int64})(ar::AbstractArray{MyType})
> return sum(ar; dims=f.k.dims)
> end
> \`\`\`
> 
> which would result in any use of \`Fix(sum; dims=1)\` operating on
> \`Vector{MyType}\` to call this special function.
> 
> \</details\>
> 
> \*\*\<details\>\<summary\>Real-world examples\*\*
> 
> I would like to use this in my code. Here are some examples:
> 
> \</summary\>
> 
> 
> 
> \- https://github.com/MilesCranmer/SymbolicRegression.jl/blob/ea03242d099aa189cad3612291bcaf676d77451c/src/InterfaceDynamicExpressions.jl#L177-L191
> \- https://github.com/MilesCranmer/DataDrivenDiffEq.jl/blob/ba70d94dd851d5880fa670d6325296512b7435b3/src/solve/koopman.jl#L140
> \- https://github.com/MilesCranmer/pysr\_paper/blob/b30687433fb32d3b9784fbedb1480d947dc46fc0/animations/optimization\_example.jl#L33
> \- https://github.com/MilesCranmer/DispatchDoctor.jl/blob/02b46f6060c84c632dedf5e602dd3ca6a2c72d95/src/stabilization.jl#L212
> \- https://github.com/MilesCranmer/DispatchDoctor.jl/blob/02b46f6060c84c632dedf5e602dd3ca6a2c72d95/src/stabilization.jl#L222
> \- https://github.com/MilesCranmer/DispatchDoctor.jl/blob/02b46f6060c84c632dedf5e602dd3ca6a2c72d95/src/stabilization.jl#L233
> \- https://github.com/MilesCranmer/UniAdminTools.jl/blob/0f523364fc0f43446ff7c31f6ae3acf6c1ea927f/src/mergescore.jl#L289-L292
> \- https://github.com/MilesCranmer/DataDrivenDiffEq.jl/blob/ba70d94dd851d5880fa670d6325296512b7435b3/docs/examples/5\_michaelis\_menten.jl#L30
> \- https://github.com/MilesCranmer/DispatchDoctor.jl/blob/02b46f6060c84c632dedf5e602dd3ca6a2c72d95/test/llvm\_ir\_tests.jl#L8
> 
> For example,
> 
> \`\`\`julia
> function michaelis\_menten(X::AbstractMatrix, p, t::AbstractVector)
> reduce(hcat, map((x,ti)-\>michaelis\_menten(x, p, ti), eachcol(X), t))
> end
> \`\`\`
> 
> which could now be done with \`map(Fix{2}(michaelis\_menten, p), eachcol(X), t)\`, reducing compilation costs, and avoiding any potential issues with capturing variables in the closure.
> 
> Another one would be this:
> 
> \`\`\`julia
> candidate\_info\_data = DataFrame((
> name = string.(candidates),
> score = (x -\> round(x, digits = 3)).(summary\_scores.mean),
> uncertainty = (x -\> round(x, digits = 2)).(summary\_scores.std),
> q25 = (x -\> round(x, digits = 3)).(summary\_scores\_q\[!, "25.0%"\]),
> q75 = (x -\> round(x, digits = 3)).(summary\_scores\_q\[!, "75.0%"\]),
> ))
> \`\`\`
> 
> with this you could write \`Fix(round; digits=3)\` and not need to re-compile an anonymous function for each new outer method.
> 
> \</details\>
> 
> 
> 
> \### Features in other languages
> 
> Here are some of the most related features in other languages (all that I could find; there's probably more)
> 
> \#### Groovy's \`.ncurry\`
> 
> \<details\>\<summary\>(Expand)\</summary\>
> 
> In Apache Groovy there is the \`\<function\>.ncurry(index, args...)\` to insert arguments at a given index. This syntax is semantically identical to \`Base.Fix\`.
> 
> From the \[documentation\](https://web.archive.org/web/20240522202230/https://groovy-lang.org/closures.html#\_index\_based\_currying)
> \> In case a closure accepts more than 2 parameters, it is possible to set an arbitrary parameter using ncurry:
> \>
> \> \`\`\`groovy
> \> def volume = { double l, double w, double h -\> l\*w\*h }
> \> def fixedWidthVolume = volume.ncurry(1, 2d)
> \> assert volume(3d, 2d, 4d) == fixedWidthVolume(3d, 4d)
> \> def fixedWidthAndHeight = volume.ncurry(1, 2d, 4d)
> \> assert volume(3d, 2d, 4d) == fixedWidthAndHeight(3d)
> \> \`\`\`
> \>
> \> 1. the \`volume\` function defines 3 parameters
> \> 2. \`ncurry\` will set the second parameter (index = 1) to 2d, creating a new volume function which accepts length and height
> \> 3. that function is equivalent to calling \`volume\` omitting the width
> \> 4. it is also possible to set multiple parameters, starting from the specified index
> \> 5. the resulting function accepts as many parameters as the initial one minus the number of parameters set by \`ncurry\`
> 
> \</details\>
> 
> \#### Python's \`functools.partial\`
> 
> \<details\>\<summary\>(Expand)\</summary\>
> 
> In Python, there is no differentiating between args and kwargs – every function can be passed kwargs. Therefore, \`functools.partial\` is semantically similar to \`Fix\`:
> 
> \`\`\`python
> def f(a, b, c, d):
> return a + b \* c - d
> 
> f\_with\_b = functools.partial(f, b=2.0)
> 
> f\_with\_b(1.0, d=3.0)
> \`\`\`
> 
> which would be equivalent to \`Fix{2}(f, 2.0)\`.
> 
> \</details\>
> 
> \#### C++'s \`std::bind\`
> 
> \<details\>\<summary\>(Expand)\</summary\>
> 
> In modern C++, one can use \`std::bind\` to insert \[placeholders\](https://en.cppreference.com/w/cpp/utility/functional/bind). This is semantically closer to an anonymous function in Julia, though it \*binds\* the arguments in a way similar to \`Fix1\` and \`Fix2\` do:
> 
> \`\`\`cpp
> void f(int n1, int n2, int n3, const int& n4, int n5);
>  
> int main() {
> using namespace std::placeholders
> 
> auto f2 = std::bind(f, \_3, std::bind(g, \_3), \_3, 4, 5);
> f2(10, 11, 12) // f(12, g(12), 12, 4, 5)
> }
> \`\`\`
> 
> \</details\>
> 
> The C++ approach was also briefly mentioned on discourse \[back in 2018\](https://discourse.julialang.org/t/fix1-analogue-of-base-fix2/10161/12).
> 
> \---
> 
> Closes:
> 
> \- https://github.com/JuliaLang/julia/issues/50553
> \- https://github.com/JuliaLang/julia/issues/36181
> 
> Related issues:
> 
> \- Initial discussion on Fix1 on \[discourse\](https://discourse.julialang.org/t/fix1-analogue-of-base-fix2/10161/9) which @tpapp added in https://github.com/JuliaLang/julia/pull/26708
> \- https://github.com/JuliaLang/julia/issues/15276
> 
> Other approaches:
> 
> \- \[FixArgs.jl\](https://github.com/goretkin/FixArgs.jl) which stemmed out of https://github.com/JuliaLang/julia/issues/36181
> - Note that this package takes a \*\*much\*\* different and more extensive approach to this problem using macros (see \[docs\](https://goretkin.github.io/FixArgs.jl/dev/#Symbolic-computation-and-lazy-evaluation)), so is likely not in-scope for merging to \`Base\`. I have written the \`Base.Fix\` in this PR from scratch based on the same patterns as \`Fix1\` and \`Fix2\` but with varargs.
> \- \[AccessorsExtra.jl\](https://github.com/JuliaAPlavin/AccessorsExtra.jl)
> - The \`FixArgs\` implementation in this package is much more closely related to this PR. It works in a similar way although though stores the full signature with a \`Placeholder()\` set to the replaced arg.
> \- \[FastBroadcast.jl\](https://github.com/YingboMa/FastBroadcast.jl) defines an identical (aside from keywords) struct \[here\](https://github.com/YingboMa/FastBroadcast.jl/blob/9077379705b3d7d1677188c1e18d841daccdbe18/src/FastBroadcast.jl#L14-L18) for internal use
> 
> Semi-related issues:
> 
> \- https://github.com/JuliaLang/julia/issues/554
> \- https://github.com/JuliaLang/julia/issues/5571
> \- https://github.com/JuliaLang/julia/pull/24990
> \- https://github.com/JuliaLang/julia/pull/36093 
> \---
> 
> \- \*\*Edit 1\*\*: ~~Made \`Fix\` work for \`Vararg\` so that you can insert multiple arguments at the specified index.~~
> \- \*\*Edit 2\*\*: Added keyword ~~s~~ to \`Fix\`.
> \- \*\*Edit 3\*\*: Switched from \`Fix(f, Val(1), arg)\` syntax to \`Fix{1}(f, arg)\`.
> \- \*\*Edit 4\*\*: After triage, added back the keyword argument ~~s~~ , and rewrote \`Fix1\` and \`Fix2\` in terms of \`Fix\`.
> \- \*\*Edit 5\*\*: Added some validation checks for repeated keywords and non-\`Int64\` \`n\`
> \- \*\*Edit 6\*\*: Restricted the number of keyword arguments OR arguments to 1, and made struct more minimal.
> \- \*\*Edit 7\*\*: After second triage, various cleanup and simplification of code
> \- \*\*Edit 8\*\*: Removed the keyword argument. Now \`Fix{n}\` is exclusively for a single positional keyword argument.

---

<div class="post-metadata">

### Author: ![oxinabox](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oxinabox/32/206603_2.png) [@oxinabox](https://discourse.julialang.org/u/oxinabox)
#### Post date: [September 3, 2024, 3:25pm UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997/3 "2024-09-03T15:25:33Z")

</div>

Fix1 and Fix2 are not related to the accidental boxing thing which is in general the main performance issue with closures and why people use let blocks or FastClosures.jl.  
Any anon function that can be written to use Fix1 or Fix2 is pretty much certain not to be possible to write in a way that runs into that issue.

I suspect the SciML style-guide suggests avoiding them either

- Purely as a stylistic preference: that it was felt to be easier to read;
- or as a microoptimization on compile times. Since identical anon functions do still compile sperately but `Fix1` and `Fix2` uses do not

But you would need to talk to author of that rule to know

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [September 3, 2024, 3:32pm UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997/4 "2024-09-03T15:32:12Z")

</div>

This is a confused post.

> [@Deduction42](#):
>
> I thought most of these performance issues were fixed with v1.0

AFAIK some cases are fixed with each new Julia release, but a complete fix will probably only happen when the lowering gets rewritten in Julia, @c42f. Relevant issue:

> <https://github.com/JuliaLang/julia/issues/15276>
>
> \`\`\` jl
> using Images: realtype
> 
> function ifi{T\<:Real,K,N}(img::AbstractArray{T,N}…, kern::AbstractArray{K,N}, border::AbstractString, value)
> if border == "circular" && size(img) == size(kern)
> out = real(ifftshift(ifft(fft(img).\*fft(kern))))
> elseif border != "inner"
> prepad = \[div(size(kern,i)-1, 2) for i = 1:N\]
> postpad = \[div(size(kern,i), 2) for i = 1:N\]
> fullpad = \[nextprod(\[2,3\], size(img,i) + prepad\[i\] + postpad\[i\]) - size(img, i) - prepad\[i\] for i = 1:N\]
> A = padarray(img, prepad, fullpad, border, convert(T, value))
> krn = zeros(typeof(one(T)\*one(K)), size(A))
> indexesK = ntuple(d-\>\[size(krn,d)-prepad\[d\]+1:size(krn,d);1:size(kern,d)-prepad\[d\]\], N)::NTuple{N,Vector{Int}}
> AF = ifft(fft(A).\*fft(krn))
> out = Array(realtype(eltype(AF)), size(img))
> end
> out
> end
> \`\`\`
> 
> Test:
> 
> \`\`\` jl
> julia\> @code\_warntype ifi(rand(3,3), rand(3,3), "replicate", 0)
> Variables:
> #self#::#ifi
> img::Array{Float64,2}
> kern::Array{Float64,2}
> border::ASCIIString
> value::Int64
> prepad::Box
> ...
> \`\`\`
> 
> Now comment out the \`indexesK = ...\` line (the output of which is not used at all). Suddenly \`prepad\` is inferred as \`Array{Int, 1}\`.

> [@Deduction42](#):
>
> Fix1/Fix2 aren’t exported from base

This is of no significance. It’s just polite not to export from `Base`, so as to prevent name space pollution, and prevent breaking existing code.

---

<div class="post-metadata">

### Author: ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)
#### Post date: [September 3, 2024, 4:23pm UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997/5 "2024-09-03T16:23:56Z")

</div>

> [@oxinabox](#):
>
> I suspect the SciML style-guide suggests avoiding them either
> 
> - Purely as a stylistic preference: that it was felt to be easier to read;
> - or as a microoptimization on compile times. Since identical anon functions do still compile sperately but `Fix1` and `Fix2` uses do not

I believe SciML folks have expressed on this forum the opinion that closures are simply too dangerous (performance-wise) to ever use, so they recommend never using them. (I personally think that viewpoint is extreme.)

---

<div class="post-metadata">

### Author: ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)
#### Post date: [September 3, 2024, 5:20pm UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997/6 "2024-09-03T17:20:28Z")

</div>

I don’t see any problem in using either of them, both have benefits.  
Fix1/Fix2/Fix:

- can dispatch other functions on them, eg:

```julia
julia> using InverseFunctions
# impossible with anonymous function:
julia> inverse(Base.Fix2(+, 123)) |> dump
Base.Fix2{typeof(-), Int64}(-, 123) (function of type Base.Fix2{typeof(-), Int64})
  f: - (function of type typeof(-))
  x: Int64 123

```

- somewhat faster to compile

Anonymous function:

- more general
- potentially faster to execute (due to constprop)

---

<div class="post-metadata">

### Author: ![greatpet](https://avatars.discourse-cdn.com/v4/letter/g/e495f1/32.png) [@greatpet](https://discourse.julialang.org/u/greatpet)
#### Post date: [September 3, 2024, 9:21pm UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997/7 "2024-09-03T21:21:23Z")

</div>

> [@oxinabox](#):
>
> or as a microoptimization on compile times.

Or maybe it’s needed to enable static compilation? (Even though this is a niche use case of Julia.)

---

<div class="post-metadata">

### Author: ![Deduction42](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/deduction42/32/9206_2.png) [@Deduction42](https://discourse.julialang.org/u/Deduction42)
#### Post date: [September 4, 2024, 2:10am UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997/8 "2024-09-04T02:10:19Z")

</div>

Yes, this is a confused post, because I was genuinely confused. Thanks everyone for the input.

---

<div class="post-metadata">

### Author: ![danielwe](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielwe/32/35657_2.png) [@danielwe](https://discourse.julialang.org/u/danielwe)
#### Post date: [September 4, 2024, 3:21am UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997/9 "2024-09-04T03:21:09Z")

</div>

> [@oxinabox](#):
>
> Any anon function that can be written to use Fix1 or Fix2 is pretty much certain not to be possible to write in a way that runs into that issue.

Is that true? Take the pitfall example from the docs; the boxing can be eliminated by replacing the closure with `Base.Fix2`, improving performance by a factor of 7. To my understanding, the key issue is whether the captured variable is reassigned at any point in the scope from which it’s captured, which is a property of the outer function, not the closure.

```julia-repl
julia> function abmult(r::Int)
           if r < 0
               r = -r
           end
           f = x -> x * r
           return f
       end;

julia> function abmultfix(r::Int)
           if r < 0
               r = -r
           end
           f = Base.Fix2(*, r)
           return f
       end;

julia> f = abmult(4);

julia> ffix = abmultfix(4);

julia> @code_warntype f(2) # Boxed, type unstable
MethodInstance for (::var"#1#2")(::Int64)
  from (::var"#1#2")(x) @ Main REPL[1]:5
Arguments
  #self#::var"#1#2"
  x::Int64
Locals
  r::Union{}
Body::Any
1 ─ %1 = Core.getfield(#self#, :r)::Core.Box
│ %2 = Core.isdefined(%1, :contents)::Bool
└── goto #3 if not %2
2 ─ goto #4
3 ─ Core.NewvarNode(:(r))
└── r
4 ┄ %7 = Core.getfield(%1, :contents)::Any
│ %8 = (x * %7)::Any
└── return %8

julia> @code_warntype ffix(2) # Fix2, type stable
MethodInstance for (::Base.Fix2{typeof(*), Int64})(::Int64)
  from (f::Base.Fix2)(y) @ Base operators.jl:1135
Arguments
  f::Base.Fix2{typeof(*), Int64}
  y::Int64
Body::Int64
1 ─ %1 = Base.getproperty(f, :f)::Core.Const(*)
│ %2 = Base.getproperty(f, :x)::Int64
│ %3 = (%1)(y, %2)::Int64
└── return %3

julia> using BenchmarkTools

julia> @btime $f(2);
  35.534 ns (0 allocations: 0 bytes)

julia> @btime $ffix(2);
  4.780 ns (0 allocations: 0 bytes)

```

---

<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: [September 4, 2024, 5:25am UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997/11 "2024-09-04T05:25:54Z")

</div>

> [@Deduction42](#):
>
> Yes, this is a confused post, because I was genuinely confused. Thanks everyone for the input.

It’s so fine to be confused and to ask questions. That is what this forum is for ❤

(Not only that, but this particular subject is just very confusing in general. Not a lot of people appreciate just how difficult fixing this is! Even FastClosures.jl has subtly broken semantics, I think, but I’ve not worked on it for a long while.)

I’m occasionally concerned people seem to think I know exactly how to fix the general problem but noope, I do not 😆

I have a few little ideas, but IIUC lowering just doesn’t have enough information for this. Perhaps with cooperation between both lowering and the optimizer/type inference we can get close to really fixing this, eventually.

I expect to be rewriting the corresponding part of lowering in November-ish so I’ll have a better appreciation for the challenges at that point.

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [September 4, 2024, 7:00am UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997/12 "2024-09-04T07:00:27Z")

</div>

> [@Deduction42](#):
>
> Yes, this is a confused post, because I was genuinely confused.

Sorry, I was rude.

---

<div class="post-metadata">

### Author: ![oxinabox](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oxinabox/32/206603_2.png) [@oxinabox](https://discourse.julialang.org/u/oxinabox)
#### Post date: [September 6, 2024, 1:09pm UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997/13 "2024-09-06T13:09:38Z")

</div>

oof, I forgot about that detail.  
you are correct

---

<div class="post-metadata">

### Author: ![Deduction42](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/deduction42/32/9206_2.png) [@Deduction42](https://discourse.julialang.org/u/Deduction42)
#### Post date: [September 6, 2024, 2:39pm UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997/14 "2024-09-06T14:39:21Z")

</div>

THIS was what really confused me. How does Fix2(…) solve the boxed variable problem? I tried something like deepcopy. If you copy “r” you shouldn’t need it anymore, copies of “r” aren’t reassigned. But it still boxes r.

```julia
julia> function abmult(r::Int)
                  if r < 0
                      r = -r
                  end
                  f(x) = deepcopy(r)*x
                  return f
              end
abmult (generic function with 1 method)

julia> f = abmult(4); @code_warntype f(2)

MethodInstance for f(::Int64)
  from f(x) @ Main REPL[4]:5
Arguments
  #self#::typeof(f)
  x::Int64
Locals
  r::Union{}
Body::Any
1 ─ %1 = Core.getfield(#self#, :r)::Core.Box
│ %2 = Core.isdefined(%1, :contents)::Bool
└── goto #3 if not %2
2 ─ goto #4
3 ─ Core.NewvarNode(:(r))
└── r
4 ┄ %7 = Core.getfield(%1, :contents)::Any
│ %8 = Main.deepcopy(%7)::Any
│ %9 = (%8 * x)::Any
└── return %9

```

---

<div class="post-metadata">

### Author: ![Deduction42](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/deduction42/32/9206_2.png) [@Deduction42](https://discourse.julialang.org/u/Deduction42)
#### Post date: [September 6, 2024, 2:45pm UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997/15 "2024-09-06T14:45:42Z")

</div>

Oh I just realized deepcopy still gets applied every time that function runs. So if I just create another variable and add it to the expression, boxing doesn’t happen. THAT’s how Fix2 solves the problem.

```julia
julia> function abmult(r::Int)
                  if r < 0
                      r = -r
                  end
                  r1 = deepcopy(r); f(x) = r1*x
                  return f
              end
abmult (generic function with 1 method)

julia> f = abmult(4); @code_warntype f(2)
MethodInstance for (::var"#f#3"{Int64})(::Int64)
  from (::var"#f#3")(x) @ Main REPL[7]:5
Arguments
  #self#::var"#f#3"{Int64}
  x::Int64
Body::Int64
1 ─ %1 = Core.getfield(#self#, :r1)::Int64
│ %2 = (%1 * x)::Int64
└── return %2

```

So I guess that means if you’re working with closures, don’t capture a variable that gets reassigned to (or at least be VERY careful about it). I’m actually very careful about reassigning variables anyway because I got burned by type instabilities before. I can see why you wouldn’t want to capture something like this.

---

<div class="post-metadata">

### Author: ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)
#### Post date: [June 6, 2025, 12:49pm UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997/16 "2025-06-06T12:49:49Z")

</div>

Hope it is OK to revive this topic with a question instead of opening another one.

If there is no assignment, just a chain of nested function calls, is it OK to use closures then?

Or, from a practical perspective, if tooling (JET, profiling etc) does not show type instability, then one is not bitten by this issue (whatever the issue is)?

I found this topic because I got a PR to a repo of mine from a person who refers to the [SciML style guide about avoiding closures](https://docs.sciml.ai/SciMLStyle/stable/#Closures-should-be-avoided-whenever-possible). The whole point of the PR is to replace all closures with `Fix`whatever.

As far as I could discern there is no issue with the particular pieces of code (which I will not link here, as I don’t want to single out the submitter), the justification is simply “closures should be avoided because SciML says so”. I understand [#15276](https://github.com/JuliaLang/julia/issues/15276) and it definitely does not apply. Is there any other issue which highlights a problem with closures? I am completely baffled by the lengths some people go to to avoid them.

---

<div class="post-metadata">

### Author: ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)
#### Post date: [June 6, 2025, 3:58pm UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997/17 "2025-06-06T15:58:17Z")

</div>

Closures are a great core feature of Julia! It’s fine for some people/organizations to decide they want to avoid them, but generally closures are extremely common in code. They also often are more performant than Fix1/2.

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [June 6, 2025, 10:31pm UTC](https://discourse.julialang.org/t/state-of-closures-fix1-fix2/118997/18 "2025-06-06T22:31:51Z")

</div>

Agreeing with both of you, closures are awesome. However, using named stuff, like `Fix1` or `identity` or `Returns`, when possible, seems like good style. A justification is that each new closure has new identity and the compiler has to parse and compile it again, even if it already compiled other equivalent closures. This:

```julia-repl
julia> (x -> 3) === (x -> 3)
false

julia> Returns(3) === Returns(3)
true

```
