# Are calls to no-op functions passed as arguments always compiled away?

**URL:** <https://discourse.julialang.org/t/are-calls-to-no-op-functions-passed-as-arguments-always-compiled-away/9494>\
**Category:** General Usage\
**Created:** [March 5, 2018, 5:28am UTC](https://discourse.julialang.org/t/are-calls-to-no-op-functions-passed-as-arguments-always-compiled-away/9494 "2018-03-05T05:28:30Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![colintbowers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/colintbowers/32/8033_2.png) [@colintbowers](https://discourse.julialang.org/u/colintbowers)\
**Post date:** [March 5, 2018, 5:28am UTC](https://discourse.julialang.org/t/are-calls-to-no-op-functions-passed-as-arguments-always-compiled-away/9494/1 "2018-03-05T05:28:30Z")

</div>

I really like the current trend in `Base` of functions that take a function as their first argument, and have been adopting it for my own code. My question is: If I define the no-op function

```julia
f(x) = x 

```

can I happily pass this into other functions of the form:

```julia
function f1(f::Function, x1, x2, ...)
   ... #some stuff
   z1 = f(x)
   ... #some other stuff
end

```

and get _zero_ efficiency loss versus the case where I don’t include a function as an argument?

My understanding is that in the above pseudo-code, the `z1 = f(x)` is compiled down to `z1 = x`, and so this is indeed the case, but I wanted to double-check before adopting this style wholesale. (yes I did try looking at `code_lowered` type output, but managed to quickly confuse myself, and also wasn’t certain if what I was seeing would hold up in more complex cases).

Bonus question: does julia have a standard no-op function for use in cases like this, eg say if I wanted to define the shortcut method:

```julia
    f1(x1, x2, ...) = f1(Base.f_noop, x1, x2, ...)

```

Thanks to all responders,

Colin

---

<div class="post-metadata">

**Author:** ![jrevels](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jrevels/32/10393_2.png) [@jrevels](https://discourse.julialang.org/u/jrevels)\
**Post date:** [March 5, 2018, 5:56am UTC](https://discourse.julialang.org/t/are-calls-to-no-op-functions-passed-as-arguments-always-compiled-away/9494/2 "2018-03-05T05:56:37Z")

</div>

> does julia have a standard no-op function for use in cases like this

`Base.identity` might be what you’re looking for.

They certainly can be, if things get properly inlined/the compiler infers that it can remove the call:

```julia
julia> f(g, x, y) = g(x) + g(y)
f (generic function with 1 method)

julia> @code_typed f(identity, 1, 1)
CodeInfo(:(begin
        return (Base.add_int)(x, y)::Int64
    end))=>Int64

julia> @code_typed f(sin, 1, 1)
CodeInfo(:(begin
        $(Expr(:inbounds, false))
        # meta: location math.jl sin 421
        SSAValue(3) = (Base.sitofp)(Float64, x)::Float64
        # meta: location math.jl sin 419
        SSAValue(5) = $(Expr(:foreigncall, ("sin", "libopenlibm"), Float64, svec(Float64), SSAValue(3), 0))
        # meta: location math.jl nan_dom_err 300
        unless (Base.and_int)((Base.ne_float)(SSAValue(5), SSAValue(5))::Bool, (Base.not_int)((Base.ne_float)(SSAValue(3), SSAValue(3))::Bool)::Bool)::Bool goto 10
        #temp#@_6 = (Base.Math.throw)($(QuoteNode(DomainError())))::Union{}
        goto 12
        10:
        #temp#@_6 = SSAValue(5)
        12:
        # meta: pop location
        # meta: pop location
        # meta: pop location
        $(Expr(:inbounds, :pop))
        $(Expr(:inbounds, false))
        # meta: location math.jl sin 421
        SSAValue(0) = (Base.sitofp)(Float64, y)::Float64
        # meta: location math.jl sin 419
        SSAValue(2) = $(Expr(:foreigncall, ("sin", "libopenlibm"), Float64, svec(Float64), SSAValue(0), 0))
        # meta: location math.jl nan_dom_err 300
        unless (Base.and_int)((Base.ne_float)(SSAValue(2), SSAValue(2))::Bool, (Base.not_int)((Base.ne_float)(SSAValue(0), SSAValue(0))::Bool)::Bool)::Bool goto 26
        #temp#@_5 = (Base.Math.throw)($(QuoteNode(DomainError())))::Union{}
        goto 28
        26:
        #temp#@_5 = SSAValue(2)
        28:
        # meta: pop location
        # meta: pop location
        # meta: pop location
        $(Expr(:inbounds, :pop))
        return (Base.add_float)(#temp#@_6, #temp#@_5)::Float64
    end))=>Float64

```

---

<div class="post-metadata">

**Author:** ![colintbowers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/colintbowers/32/8033_2.png) [@colintbowers](https://discourse.julialang.org/u/colintbowers)\
**Post date:** [March 5, 2018, 9:26am UTC](https://discourse.julialang.org/t/are-calls-to-no-op-functions-passed-as-arguments-always-compiled-away/9494/3 "2018-03-05T09:26:16Z")

</div>

Ah, `identity`, I should have thought to try that.

Many thanks, I’ll mess around with `@code_typed` a bit. The example you showed is very clear.

Cheers,

Colin

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [March 5, 2018, 1:30pm UTC](https://discourse.julialang.org/t/are-calls-to-no-op-functions-passed-as-arguments-always-compiled-away/9494/4 "2018-03-05T13:30:51Z")

</div>

As a word of warning, there is still something broken with singletons on 0.7 master. See [https://github.com/JuliaLang/julia/issues/25983](https://github.com/JuliaLang/julia/issues/25983).

```julia
using BenchmarkTools;
f(x)=x;

@code_native f(nothing)
	.text
; Function f {
; Location: REPL[2]:1
	movq	%rsi, -8(%rsp)
	movabsq	$139684048367624, %rax # imm = 0x7F0ABA157008
	retq
;}

x=fill(nothing, 1000); y=copy(x); sizeof(x)
0

@btime $y .= f.($x);
30.457 ��s (489 allocations: 7.64 KiB)

@btime $y .= identity.($x);
15.317 ns (0 allocations: 0 bytes)

```

If you have no missing data or don’t care about speed in the next couple of weeks until this gets fixed (not by me, I don’t dare touch codegen), then you have no problem; but this issue makes some benchmarks misleading in the meantime.

edit: Fixed now in [https://github.com/JuliaLang/julia/pull/26322](https://github.com/JuliaLang/julia/pull/26322). Merci to @kristoffer.carlsson and vtjnash!

---

<div class="post-metadata">

**Author:** ![colintbowers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/colintbowers/32/8033_2.png) [@colintbowers](https://discourse.julialang.org/u/colintbowers)\
**Post date:** [March 5, 2018, 10:46pm UTC](https://discourse.julialang.org/t/are-calls-to-no-op-functions-passed-as-arguments-always-compiled-away/9494/5 "2018-03-05T22:46:36Z")

</div>

Good to know, thank you. Fortunately, I don’t need to worry about `missing` for now.

Cheers,

Colin

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [March 5, 2018, 10:52pm UTC](https://discourse.julialang.org/t/are-calls-to-no-op-functions-passed-as-arguments-always-compiled-away/9494/6 "2018-03-05T22:52:59Z")

</div>

Functions all have their own types. Just like how Julia specializes on `1 + 1` and `1.0 + 1.0` to call integer and floating point addition, respectively, when you call `f1(identity, x, y, z)`, it’ll specialize on the types of those arguments and do all sorts of optimizations since it knows what all the types are. You can even _dispatch_ on specific function types:

```julia
julia> f(::typeof(identity)) = 1
       f(::typeof(sin)) = 2
f (generic function with 2 methods)

julia> f(identity)
1

julia> f(sin)
2

julia> f(cos)
ERROR: MethodError: no method matching f(::typeof(cos))
Closest candidates are:
  f(::typeof(sin)) at REPL[1]:2
  f(::typeof(identity)) at REPL[1]:1

```

---

<div class="post-metadata">

**Author:** ![colintbowers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/colintbowers/32/8033_2.png) [@colintbowers](https://discourse.julialang.org/u/colintbowers)\
**Post date:** [March 5, 2018, 11:17pm UTC](https://discourse.julialang.org/t/are-calls-to-no-op-functions-passed-as-arguments-always-compiled-away/9494/7 "2018-03-05T23:17:54Z")

</div>

That’s a neat trick! I had no idea you could do that. I’m guessing this is a fairly new feature? I seem to remember a year or two ago that `typeof(sum)` would evaluate to `Function`, so that `typeof(sum) == typeof(identity)` would evaluate to `true`. (I just verified for myself then that it now evaluates to `false`)

So every defined function can now implicitly be thought of as its own type? Or perhaps a better analogy would be its own parametric type… something like `Function{T} where {T<:Union{sum, identity, ...}}`, so that we can still write things like `f1(f::Function, x)` and have it work for any function `f`?

I just played around a bit and realised the same holds for anonymous functions too, and you can dispatch on them, with the caveat that `f1 = (x -> identity(x))` and `f2 = (x -> identity(x))`, are different function types. Does this mean that anonymous functions are all created and stored in global scope, such that you couldn’t write a loop that creates anonymous functions indefinitely, since they would never be garbage collected?

Sorry, I just realised that is a lot of questions. I should probably re-read the manual at some point. I think the last version I read was v0.3…

Cheers,

Colin

---

<div class="post-metadata">

**Author:** ![rdeits](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rdeits/32/286_2.png) [@rdeits](https://discourse.julialang.org/u/rdeits)\
**Post date:** [March 6, 2018, 2:04am UTC](https://discourse.julialang.org/t/are-calls-to-no-op-functions-passed-as-arguments-always-compiled-away/9494/8 "2018-03-06T02:04:02Z")

</div>

The manual is definitely your friend here, but this has changed a lot in the last couple of years 😄

Before Julia v0.5, `Function` was the concrete type of every function (as you remember correctly), and anonymous functions were inherently slower than ordinary functions. As of Julia v0.5, `Function` is now an abstract type, and each named and anonymous function is a separate concrete type which is `<: Function`. You can certainly still write `f1(f::Function, x)` and any named or anonymous function will work for `f`, but now you can also dispatch on the type of a particular function (if you want). This change is also what made anonymous functions just as fast as regular functions, which in turn enabled fast broadcast fusion and lots of other fun features. Here’s the most relevant PR: [WIP: redesign closures, then generic functions by JeffBezanson · Pull Request #13412 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/pull/13412)

As of v0.5 and above, an anonymous function creates a new, callable type. Closures are just callable types with fields containing their closed-over values. You can actually see this:

```julia
# We have to put this in a function in order to create a real 
# closure instead of just a function that references some global
# variable named `i`
julia> function closure_demo()
         i = 1
         f = x -> x + i
       end
closure_demo (generic function with 1 method)

julia> f = closure_demo()
(::#11) (generic function with 1 method)

julia> f.i
1

julia> f(2)
3

```

As far as I know, the compiled code from a function is indeed never garbage collected. However, that code is only generated once per anonymous function definition. So, for example, we can do:

```julia
julia> fs = [x -> x + i for i in 1:10]
f10-element Array{##18#20{Int64},1}:
 #18
 #18
 #18
 #18
 #18
 #18
 #18
 #18
 #18
 #18

julia> fs[1].i
1

julia> fs[2].i
2

```

Each element of `fs` is just a lightweight instance of the same type (`#18`) with a different captured value of `i`. They all share the same compiled code.

On the other hand, if you were to create lots of new functions in a loop (you’d have to do something like call `eval()` in your loop to do this), then you would indeed run into trouble because the compiled code for those functions would not be garbage collected.

---

<div class="post-metadata">

**Author:** ![colintbowers](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/colintbowers/32/8033_2.png) [@colintbowers](https://discourse.julialang.org/u/colintbowers)\
**Post date:** [March 6, 2018, 4:39am UTC](https://discourse.julialang.org/t/are-calls-to-no-op-functions-passed-as-arguments-always-compiled-away/9494/9 "2018-03-06T04:39:21Z")

</div>

I knew that anonymous functions had been made fast, but I had no understanding of how they did it. That is a fantastic write-up of it, thank you very much. If you’re on StackOverflow, I’d be happy to post this as a question, and you can cut-and-paste your answer from here, since I think it is a great resource.

Cheers,

Colin
