How to pass in a function as an argument to a function?

Hello

So I have this function -

function late_time_value(slope, x_init::Float64, y_init::Float64, dx::Float64 = 0.01, tf::Float64 = 100)
    N = floor(Integer, tf/dt)
    sol_x = zeros(N)
    sol_y = zeros(N)
    
    sol_x[1] = x_init
    sol_y[1] = y_init
    
    for i in 1:N-1
        sol_x[i+1] = sol_x[i] + dx
        sol_y[i+1] = sol_y[i] + slope(sol_y[i]) * dx
    end
    
    return sol_x, sol_y
end

And for slope I passed in a function slope defined as slope(x) = sin(x). But Julia throws this error -

julia> late_time_value(slope, 0., 0.5)
MethodError: no method matching late_time_value(::typeof(slope), ::Float64, ::Float64, ::Float64, ::Int64)
Closest candidates are:
  late_time_value(::Any, ::Float64, ::Float64, ::Float64) at In[5]:1
  late_time_value(::Any, ::Float64, ::Float64, ::Float64, !Matched::Float64) at In[5]:1
  late_time_value(::Any, ::Float64, ::Float64) at In[5]:1

Stacktrace:
 [1] late_time_value(slope::Function, x_init::Float64, y_init::Float64)
   @ Main ./In[5]:2
 [2] top-level scope
   @ In[8]:1
 [3] eval
   @ ./boot.jl:360 [inlined]
 [4] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
   @ Base ./loading.jl:1090

However when I do something a bit more simple it works.

julia> function f(s)
           return s()
       end
f (generic function with 1 method)

julia> g() = 2
g (generic function with 1 method)

julia> f(g)
2

What’s going on here? I would appreciate any help

1 Like

The error message is a bit confusing.

You have tf::Float64 = 100, but it should be tf::Float64 = 100.0.

The error message gives a clue in the last argument: late_time_value(::typeof(slope), ::Float64, ::Float64, ::Float64, ::Int64).

2 Likes

Yep, that was it. My mistake, thanks. After setting df::Float64 = 100. it runs successfully.
Strange, I wouldn’t have expected that to affect anything, but I guess it makes sense with multiple dispatch.

Thank you for the help!

1 Like

You also may want to remove the type annotations all together. They don’t improve the performance and it makes the code less general. General rule of thumb I follow is to avoid type annotations unless your code specifically needs them in order to run correctly. In your example the only type annotation I would have is an annotation on the slope variable to constrain it to be a function e.g. slope::Function

I’m surprised “Don’t overuse type annotations” isn’t in the style guide.

5 Likes

Ah I wasn’t aware that they don’t improve performance. I was under the impression that Julia would use those type annotations to make further optimizations on compile time.

Does it also not matter for arrays either? Like is doing an operation on a Vector{Float64} and Vector{Integer} the same in terms of the machine code generated by Julia?

Julia will specialize for typing whether you annotate or not. Annotations help in eliminating type instabilities. Although you can have type stable code without them. You can check for such instabilities with @code_warntype.

1 Like

For inputs to functions Julia can figure out the types and specialize on them while compiling.

For defining structs or calling constructors it does affect performance.

A Vector created with Vector{Int}(undef, x) will be faster then one created with Vector{Integer}(undef, x). As in the latter case Julia has to be able to handle any type of Integer.

Likewise

struct MyStruct
  x
end

will be slower then

struct MyStruct{T}
  x::T
end

Also, I would avoid constraining to a function as that won’t work with most callable structs.

1 Like

That makes sense. Thank you for the help!!

This is a good read: Performance Tips · The Julia Language

1 Like