# Functions as a struct Field

**URL:** <https://discourse.julialang.org/t/functions-as-a-struct-field/107013>\
**Category:** General Usage\
**Tags:** question\
**Created:** [December 1, 2023, 7:09pm UTC](https://discourse.julialang.org/t/functions-as-a-struct-field/107013 "2023-12-01T19:09:19Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![Chong\_Wang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chong_wang/32/20307_2.png) [@Chong\_Wang](https://discourse.julialang.org/u/Chong_Wang)\
**Post date:** [December 1, 2023, 7:09pm UTC](https://discourse.julialang.org/t/functions-as-a-struct-field/107013/1 "2023-12-01T19:09:19Z")

</div>

I am aware of some related discussion ([Have a function field in a structure, using another field in the structure](https://discourse.julialang.org/t/have-a-function-field-in-a-structure-using-another-field-in-the-structure/74329) and [Alternative to function as field in struct](https://discourse.julialang.org/t/alternative-to-function-as-field-in-struct/55094)), but the discussion there is insufficient for my case. Therefore, it is probably useful to start a separate thread here.

If, for example, I need to define a type `AtomicOrbital` as

```julia
struct AtomicOrbital
    n::Int64
    l::Int64
    m::Int64
    radial_function::Function
end

```

where the `radial_function` should be specified by the user to describe the radial part of the atomic orbital. The definition above is not completely satisfactory, since `Function` is an abstract type and such definition will hurt performance. However, storing the `radial_function` inside the struct seems to be the most reasonable way to organize data.

To eliminate the abstract type, the above type can be defined as

```julia
struct AtomicOrbital{T<:Function}
    n::Int64
    l::Int64
    m::Int64
    radial_function::T
end

```

However, I am not completely comfortable with the above definition. One reason is that this definition does not seem to be logical to me. `r -> exp(-r)` and `r -> exp(-2r)` lead to two different concrete types, which is logically weird. Another reason is that I am not sure whether this approach will put too much pressure on the Julia compiler.

Another way to improve the performance is to define an abstract type

```julia
abstract type AbstractAtomicOrbital end

function get_radial end

```

and ask the user to defined custom types as

```julia
struct MyAtomicOrbital <: AbstractAtomicOrbital
    n::Int64
    l::Int64
    m::Int64
end

function get_radial(orbital, r)
    return exp(-r)
end

```

However, with this approach, I partially lose control of how `AtomicOrbital` is defined. If, for some reason, I want to define

```julia
function transform_orbital(orbital::AbstractAtomicOrbital)
    # do something to define new orbital
    return new_orbital
end

```

to transform one atomic orbital into another (e.g., make the radial function decay quicker). I have no idea how ` transform_orbital` should be defined, since I don’t have information how `MyAtomicOrbital` is defined by the user.

So how do people generally handle this kind of situation, which I believe is not that rare in the real world?

Another related question: why Julia does not have something like `Function{Float64,Float64}` to specify the input and output of a function? It seems to solve the above issue.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [December 1, 2023, 7:22pm UTC](https://discourse.julialang.org/t/functions-as-a-struct-field/107013/2 "2023-12-01T19:22:03Z")

</div>

> [@Chong\_Wang](#):
>
> One reason is that this definition does not seem to be logical to me. `r -> exp(-r)` and `r -> exp(-2r)` lead to two different concrete types, which is logically weird.

If you have a class of functions with some parameter, like e^{-\alpha r} for different decay rates \alpha, you can make them the same type by defining a callable `struct` for that set of functions, storing the parameter as an explicit field. For example:

```julia
struct ExpDecay <: Function
    α::Float64
end
(f::ExpDecay)(r) = exp(-f.α * r) 

```

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [December 1, 2023, 7:22pm UTC](https://discourse.julialang.org/t/functions-as-a-struct-field/107013/3 "2023-12-01T19:22:35Z")

</div>

How about a functor?

```julia
struct ExponentialRadialDecay
    k::Int
end
(erd::ExponentialRadialDecay)(r) = exp(-erd.k*r)

```

Then `k` can be a fixed parameter of the type.

```julia-repl
julia> erd1 = ExponentialRadialDecay(1)
ExponentialRadialDecay(1)

julia> erd2 = ExponentialRadialDecay(2)
ExponentialRadialDecay(2)

julia> erd1(1)
0.36787944117144233

julia> erd2(1)
0.1353352832366127
1

julia> erd2(0.5)
0.36787944117144233
1

julia> erd1(log(1))
1.0

julia> erd2(0.5log(1))
1.0

```

You can add it to `AtomicOrbital`

```julia-repl
julia> struct AtomicOrbital
           n::Int64
           l::Int64
           m::Int64
           radial_function::ExponentialRadialDecay
       end

julia> ao = AtomicOrbital(0 ,0, 0, erd1)
AtomicOrbital(0, 0, 0, ExponentialRadialDecay(1))

julia> ao.radial_function(0)
1.0

julia> ao.radial_function(log(2))
0.5

```

---

<div class="post-metadata">

**Author:** ![bertschi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bertschi/32/33462_2.png) [@bertschi](https://discourse.julialang.org/u/bertschi)\
**Post date:** [December 1, 2023, 7:28pm UTC](https://discourse.julialang.org/t/functions-as-a-struct-field/107013/4 "2023-12-01T19:28:41Z")

</div>

Also [FunctionWrappers.jl](https://github.com/yuyichao/FunctionWrappers.jl) might do what you want.

---

<div class="post-metadata">

**Author:** ![Chong\_Wang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chong_wang/32/20307_2.png) [@Chong\_Wang](https://discourse.julialang.org/u/Chong_Wang)\
**Post date:** [December 1, 2023, 7:50pm UTC](https://discourse.julialang.org/t/functions-as-a-struct-field/107013/5 "2023-12-01T19:50:07Z")

</div>

@stevengj @mkitti Thank you, but I really want `radial_function` to be completely general. \exp (-\alpha r) was only an example.

@bertschi FunctionWrappers.jl seesm to be what I needed, although I don’t really understand how it is implemented within 149 lines of code.

Currently I am using the following approach:

I still define `AtomicOrbital` as

```julia
struct AtomicOrbital
    n::Int64
    l::Int64
    m::Int64
    radial_function::Function
end

```

but I use a function barrier whenever I need to use `radial_function`. What do you think of this approach?

Edit: more explicitly, I define

```julia
function get_radial(orbital::AtomicOrbital, r::Float64)::Float64
    return orbital.radial_function(r)
end

```

and call `get_radial` instead of `orbital.radial_function` anywhere else in the code.

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [December 1, 2023, 8:07pm UTC](https://discourse.julialang.org/t/functions-as-a-struct-field/107013/6 "2023-12-01T20:07:35Z")

</div>

You probably will be safer using the concrete type for the atomic orbital field, and the functor if you need many different functions that are variations of some set of types (which is probably the case).

Otherwise you’ll carry these type instabilities in your code, and that may make your debugging hard later on.

In all cases, you will have to take care in not iterating over arrays of atomic orbitals that contain different types of functions in hot loops, because you will unavoidably (with any of the approaches) hit some dynamic dispatch, independently of the return types being the same, or the function types being concretely typed in the struct.

---

<div class="post-metadata">

**Author:** ![Chong\_Wang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chong_wang/32/20307_2.png) [@Chong\_Wang](https://discourse.julialang.org/u/Chong_Wang)\
**Post date:** [December 1, 2023, 8:24pm UTC](https://discourse.julialang.org/t/functions-as-a-struct-field/107013/7 "2023-12-01T20:24:29Z")

</div>

> [@lmiq](#):
>
> In all cases, you will have to take care in not iterating over arrays of atomic orbitals that contain different types of functions in hot loops, because you will unavoidably (with any of the approaches) hit some dynamic dispatch, independently of the return types being the same, or the function types being concretely typed in the struct.

Even with FunctionWrappers.jl? For example, `FunctionWrapper{Float64, Tuple{Float64, Float64}}` seems to be a concrete type.

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [December 1, 2023, 8:43pm UTC](https://discourse.julialang.org/t/functions-as-a-struct-field/107013/8 "2023-12-01T20:43:24Z")

</div>

The srtuct will be concrete, but I don’t think it will handle splitting the iteration on different calls. That would need a macro to be applied at the loop level.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [December 1, 2023, 9:35pm UTC](https://discourse.julialang.org/t/functions-as-a-struct-field/107013/9 "2023-12-01T21:35:18Z")

</div>

JET.jl detects runtime dispatches at a FunctionWrapper call because of a possible `reinit_wrapper` call on the contained function and its type in `::Any` fields to make a valid function pointer after instantiation during precompilation, but otherwise that pointer is already made at instantiation. I’m pretty sure in that case there’s no runtime dispatch, at least it doesn’t contribute allocations like runtime dispatches typically do.

Incidentally, use `reinit_wrapper` on existing FunctionWrapper instances after you edit methods, the function pointer is not updated automatically.

---

<div class="post-metadata">

**Author:** ![mikmoore](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mikmoore/32/31109_2.png) [@mikmoore](https://discourse.julialang.org/u/mikmoore)\
**Post date:** [December 1, 2023, 9:38pm UTC](https://discourse.julialang.org/t/functions-as-a-struct-field/107013/10 "2023-12-01T21:38:44Z")

</div>

> [@Chong\_Wang](#):
>
> Even with [FunctionWrappers.jl](https://juliahub.com/ui/Packages/FunctionWrappers)? For example, `FunctionWrapper{Float64, Tuple{Float64, Float64}}` seems to be a concrete type.

I believe that `FunctionWrappers.jl` should do what you want and be fine to store many different functions with identical signatures without causing type instabilities due to abstract `Function` supertypes or requiring dynamic dispatch.

My understanding is that a `FunctionWrapper` is concretely typed and requires no dynamic dispatch at a call site. `FunctionWrappers` takes a Julia function (with arbitrarily many methods) and uses a concrete call signature to select a single specific method instance of that function. That method instance is stored as a function pointer that is directly callable (and is called via some version of `@ccall`, probably). Thus, no dynamic dispatch is required (or even possible) at the call site.

Somebody with more knowledge or experience with `FunctionWrappers` may need to correct me on a few points.

---

<div class="post-metadata">

**Author:** ![heliosdrm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/heliosdrm/32/3851_2.png) [@heliosdrm](https://discourse.julialang.org/u/heliosdrm)\
**Post date:** [December 1, 2023, 10:07pm UTC](https://discourse.julialang.org/t/functions-as-a-struct-field/107013/11 "2023-12-01T22:07:11Z")

</div>

I would do something like this:

```julia
abstract type RadialFunctionType end

struct ExponentialDecay{T} <: RadialFunctionType
    factor::T
end

struct AnotherTypeOfFunction <: RadialFunctionType
    ...
end

radialfunction(x::ExponentialDecay) = (r -> exp(-x.factor * r))
radialfunction(x::AnotherTypeOfFunction) = ... # whatever

struct AtomicOrbital{R<:RadialFunctionType}
    n::Int64
    l::Int64
    m::Int64
    radial_function::R
end

```

Then, you can do:

```julia
function transform_orbital(orbital::AbstractAtomicOrbital{R}) where R
    # do something to define new orbital
    # (the parameter R tells what radial function is originally involved)
    return new_orbital
end

```

Does this solve your needs?

---

<div class="post-metadata">

**Author:** ![Chong\_Wang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chong_wang/32/20307_2.png) [@Chong\_Wang](https://discourse.julialang.org/u/Chong_Wang)\
**Post date:** [December 2, 2023, 12:09am UTC](https://discourse.julialang.org/t/functions-as-a-struct-field/107013/12 "2023-12-02T00:09:01Z")

</div>

Thank you. I guess this is as good as it can be.
