About type declarations

What is the difference between the following two cases (if there is any difference at all):

struct Bar{F}
  f::F
end

struct Foo
  bar::Bar
end

and:

struct Bar{F}
  f::F
end

struct Foo{F}
  bar::Bar{F}
end

Are both cases completely the same?

Thanks!

No, in the first case, the definition of Foo contains the abstract type Bar, since F is not passed as a parameter. In order for Foo to be concrete, the parameter of Bar{F} must be specified, for instance with a type variable, like in your second example.

Usually, you want structs to be defined with (possibly parameterized) concrete types for performance.

3 Likes

Let’s assume I am using the definitions given in the first case:

julia> b1 = Bar(t -> sin(t))
Bar{var"#34#35"}(var"#34#35"())

julia> Foo(b1)
Foo(Bar{var"#34#35"}(var"#34#35"()))

Now, In the second case I get:

julia> b1 = Bar(t -> sin(t))
Bar{var"#34#35"}(var"#34#35"())

julia> Foo(b1)
Foo{var"#9#10"}(Bar{var"#9#10"}(var"#9#10"()))

For performance, the second case Foo instance is better because it has a concrete type Bar instead of the abstract type Bar. This is seen in the output as well, right?

Thanks!

I believe if “f” is always going to be a function then you might just want to go:

struct Bar
    f::Function
end
struct Foo
   bar::Bar
end

No, I think that is not a good advice because Function is an abstract type and for performance we should not have a field of this type inside the object. Instead use:

struct Bar{F<:Function}
  f::F
end
3 Likes

Are you sure about that? There is no way with the Function signature. And when I do this:

function a(v::Int)::Int
    v + 1
end

function b(b::Int)::Int
    v * 2
end

function just_one(f1::F) where F <: Function
    l = Vector{F}()
    push!(l, f1)
    return l
end
function next_one!(l::Vector{F}, f1::F) where F <: Function
    push!(l, f1)
    return l
end

l = just_one(a)
next_one!(l, b)

I get:

ERROR: LoadError: MethodError: no method matching next_one!(::Array{typeof(a),1}, ::typeof(b))
Closest candidates are:
  next_one!(::Array{F,1}, ::F) where F<:Function at /home/pixel27/test.jl:16

Even though function a and b have the same signature, their Type is different. When calling just_one and passing in function a Julia would compile just_one for that parameters. Now I guess it could recompile the function if I call it passing in b as well. But I suspect the speed improvement for compiling the function specifically for the function parameter would be negated by having to recompile the function every time a different function is passed in.

The problem here is that you probably shouldn’t be storing functions in a vector, as that loses the type info of the function.

Yes, I am.

Exactly.