Type of function in struct

Hello,
I have a struct that I would like to include a function in. How should I declare its type?

Base.@kwdef mutable struct Tmp
    Δt :: Float64 = 0.3
    t :: Float64 = 0
    timestep :: Int = 0
    function = 0.0
end

By default it returns 0 Float64, but it can also return other Float64 values. However, I believe that’s no its type. What’s the type of a function? Running typeof on a function doesn’t return anything.
Thanks!

1 Like

It is a special type, that is usually printed with typeof. Eg

julia> typeof(sin)
typeof(sin)

Unless you want to be really specific, just use a type parameter, eg

struct Tmp{F}
    f::T
end
1 Like

Ah, that kind of makes sense.
Do you mean:

struct Tmp{T}
    f::T
end

I can’t seem to use this syntax with @kwdef defaults. I’d still like the function to return 0 as default.

What about _ -> 0.0?

(Yes, the F/T is a typo).

This is what I get:

Base.@kwdef mutable struct Tmp17{T}
           Δt :: Float64 = 0.3
           t :: Float64 = 0
           timestep :: Int = 0
           a :: {T} = (_ -> 0.0)
       end
ERROR: syntax: { } vector syntax is discontinued around util.jl:459
Stacktrace:
 [1] top-level scope at REPL[39]:1

You can use the Function type, e. g:

Base.@kwdef mutable struct Tmp1
    Δt::Float64 = 0.3
    t::Float64 = 0
    timestep::Int = 0
    function::Function = indentity
end
julia> Tmp1().function(10)
10
Base.@kwdef mutable struct Tmp2
    Δt::Float64 = 0.3
    t::Float64 = 0
    timestep::Int = 0
    function::Function = (_ -> 0.0)
end
julia> Tmp2().function()
0.0

Don’t call it the field function, that’s a Julia keyword and will cause confusion.

Also, you should avoid using abstract typing of fields, and Function is an abstract type. Instead, you can do

Base.@kwdef mutable struct Tmp17{T<:Function}
           Δt::Float64 = 0.3
           t::Float64 = 0
           timestep::Int = 0
           a::T = (_ -> 0.0)
       end
5 Likes

BTW, (_ -> 0.0) is a function that takes exactly one argument. If you want it to take zero arguments, you can use

() -> 0.0

If you want it to accept any number of arguments, including zero, use

(_...) -> 0.0
3 Likes

you need a::T.

That has two problems:

  1. it is an abstract field type,
  2. not all callables are subtypes of Function.
2 Likes

Thanks, but I still have an issue:

Base.@kwdef mutable struct Tmp20{T<:Function}
           Δt :: Float64 = 0.3
           t :: Float64 = 0
           timestep :: Int = 0
           a :: T = (_...) -> 0.0
       end

julia> tmp=Tmp20()
ERROR: MethodError: no method matching Tmp20(::Float64, ::Int64, ::Int64, ::var"#149#154")
Closest candidates are:
  Tmp20(::Float64, ::Float64, ::Int64, ::T) where T<:Function at REPL[43]:2
Stacktrace:
 [1] Tmp20(; Δt::Float64, t::Int64, timestep::Int64, a::Function) at .\util.jl:448
 [2] Tmp20() at .\util.jl:448
 [3] top-level scope at REPL[44]:1

Ah, yes. Thanks.

The problem is

eg use 0.0 or ::Int.

Ah, thanks. That’s weird, since I’ve been using that line for a long time. I guess the {} stuff changed some things.
Now the issue is the function is not modifiable.

Base.@kwdef mutable struct Tmp21{T<:Function}
    Δt :: Float64 = 0.3
    t :: Float64 = 0.0
    timestep :: Int = 0
    a :: T = (_...) -> 0.0
end
tmp=Tmp21()
tmp.a=(x->x*2)

ERROR: MethodError: Cannot `convert` an object of type var"#196#197" to an object of type var"#187#192"
Closest candidates are:
  convert(::Type{T}, ::T) where T at essentials.jl:171
Stacktrace:
 [1] setproperty!(::Tmp21{var"#187#192"}, ::Symbol, ::Function) at .\Base.jl:34
 [2] top-level scope at REPL[52]:1

Any ideas?

To be modifiable you need to remove the parameter (because each function has its own type) and declare that Function. But that will make the field abstract, and that will be bad for performance (if that matters).

One odd alternative would be (I don’t know if this would peform well, though):

julia> Base.@kwdef mutable struct A
         f::Ref = Ref(() -> 0)
       end
A

julia> a = A()
A(Base.RefValue{var"#2#4"}(var"#2#4"()))

julia> a.f[]()
0

julia> a.f = Ref(sin)
Base.RefValue{typeof(sin)}(sin)

julia> a.f[](π/2)
1.0
2 Likes

If you use Base.@kwdef, a parametric type T, and give a default argument for the function a, T will be the type of the default argument. If you use an anonymous function, that type will be some random var"#xyz" thing (try typeof(tmp.a) in your last example). The point is that each function has its own type. So sin is of type "typeof sin", same for cos and so on. In the mutable struct you have created it is no problem to change e.g. t to 1.0, because only the value is changed. But if you want to swap out the function and you have a parametric type, where each type is set in stone, you just cannot change a to another function, because this also changes the type not the value. There are two ways out here: 1) fall back to a::Function, which might come with performance drawbacks, as the compiler won’t be able to make certain optimizations, or 2) just leave the default argument blank a::T. Then you can create new instances of your struct with Tmp21(;a=sin), Tmp21(;a=cos), so you need a new struct for each new function, but you can always change t, timestep etc.

Looking at the struct you want to define I assume you want to containerize the parameters for some time-dependent simulation. You can also take a step back and reconsider your code, e.g. fixed time series can nicely be expressed just with ranges 0:0.1:100. Or you can also take a look at named tuples for initializing your code, e.g. params = (timespan=0:0.1:10, f=sin).

2 Likes

@lmiq and @maxfreu
Yes, performance is critical, so I need something that works very well. Redefining the entire struct if I need to update the function is not unreasonable. Maybe that’s what I’ll do.

Regarding taking a step back: what this function is supposed to be is a user defined body motion for my simulation. It actually would return a StaticArray with the velocity of each point in my geometry. That’s why it can’t be a simple range or something like that. I want to be able to have an input parameter which is a generic time-dependent motion (e.g. a rotation matrix or what have you). A function within the solver struct was the best I could come up with, but I’m open to opinions =].
Thanks a lot!

But the struct has to be mutable? From a user perspective this could be perfectly fine:

julia> struct A{T}
         p::Int
         f::T
       end

julia> a = A(1,sin)
A{typeof(sin)}(1, sin)

Why do you need mutability there?

I made it mutable because there’s a lot of stuff in there that gets updated (e.g., current timestep). The function being mutable is nice in case I want a body to do a certain motion for n timesteps and then change that function entirely for another n timesteps. But redeclaring the struct in that case is not a bad solution. Not the most convenient, but very doable.
Thanks!

Yeah, to do that probably it is better to redefine the whole structure when required, like:

julia> a = A(1,sin)
A{typeof(sin)}(1, sin)

julia> a = A(a.p,cos)
A{typeof(cos)}(1, cos)

custom constructors could make that syntax more simple, like (a = A(a,cos)) for multiple parameters.

But I understand your pain in wanting to keep several mutable fields inside an immutable struct. You may try Setfield to work with those:

julia> using Setfield

julia> @set! a.p = 2
A{typeof(cos)}(2, cos)

julia> a
A{typeof(cos)}(2, cos)
1 Like