Function in type definition

Hi folks,
this is something that has been in my mind for few days now, and that finally want to know. The question is: is it possible to include a function as part of a type definition? I mean, something of the form:

``````type koly
x::Int16
y::Float64
V::x-> x^2
end
``````

I hope you understand what I mean. Even better, assume I have an already defined function

``````function V(x::Float64)
return x*x
end
``````

could/how can I make some attribute defined in type koly point to function V?

Ferran.

1 Like

Iâ€™ve discovered I can declare in koly a variable of type Any and then do the assignment

``````type koly
x::Int16
y::Float64
f::Any
koly() = new()
end
``````

and then

``````A = koly()
A.x = 16
A.y = 14.0
A.f = V
``````

such that

``````A.f(3.0)
``````

returns 9.0 as expectedâ€¦ but is this the right way to do it? Isâ€™t declaring something as Any going against performance?

Thanks again,

Ferran.

Is this what you want?

``````type koly
x::Int16
y::Float64
V::Function
end

a = koly(1,1.0,x->x^2)
``````

Or d you want that function to be related to the first field `x` ?
This would be achieved like this

``````type koly
x::Int16
y::Float64
V::Int16
koly(x::Int16,y::Float64) = new(x,y,x^2)
end

a = koly(2,1.0)
``````

Overloading of `getfield` and `setfield!` is coming in 1.0, which will allow you to do things like

``````getfield(k::koly, f::Symbol) = (f == :V) && (return V)  # this is not a great way of doing this but it works

k = koly(1, 1.0)

k.V(x)
``````

However, this is somewhat in violation of the spirit of multiple dispatch. The recommended procedure would be to instead define

``````V(k::koly, x) = k.x + x^2  # this is just an example, in case you'd want V to involve koly somehow
``````

If youâ€™re coming from more traditional object oriented languages, this may at first seem like a bizarre anti-pattern. Once you get a bit used to it, however, you will probably learn to love and embrace it. There is very rarely any reason for objects to â€śownâ€ť functions in this way, it is simply an historical artifact of C++. Your example demonstrates that quite well (even though Iâ€™m sure it was simplified for demonstration purposes): if you just want to square a float, there is no reason for the function doing this to have any special relation to `koly`. `koly` is a type, not a namespace. If you are looking for a namespace, `module` serves that purpose.

I come from C++ originally, but now I would find it very painful to go back to the traditional object-oriented way of doing things. See also this.

Despite this, as @favba pointed out, there is nothing stopping you from storing function references as properties of structs, however Iâ€™d urge you to contemplate whether this is really what you want to do.

By the way, I may have been a little confused by exactly what you were looking to do, perhaps it was

``````V(x::Real) = x^2
V(k::koly) = V(k.x)
``````

`Function` is a valid type, so you can define V as `V::Function`

That would only do it at construction time. It would be better to do call overloading here. For example, you can do:

``````(p::koly)() = p.x^2
``````

and now the instances act as a function on the internal variable:

``````a = koly(2,1.0)
a() # Returns 4
``````

It is, but itâ€™s also an abstract type. You in some cases you may want to use a type-parameter instead in order to strictly type it:

``````type koly{F}
x::Int16
y::Float64
V::F
end
``````

without that it wonâ€™t inline well, and can be the root of some type inference issues. Another thing to look into is FunctionWrappers.jl.

4 Likes

Oh, thanks for all the quick replies I didnâ€™t knew Function was a valid data type, so this solves my problem. Do I undertand that it does not matter if this Function returns Int, Float, an Array of Intâ€™s etcâ€¦? Is this still performance-wise?
Thanks again,
Ferran.

Calling functions by reference usually performs pretty well in Julia and does not require you to know the argument or return types, but there are some exceptions. I recently had a situation in which I needed to store a couple of hundred functions in an array, access them at random, and call them very quickly. In that case, `Array{Function}` was inadequate for my needs, however the previously mentioned FunctionWrappers.jl solved that problem quite nicely, at the cost of needing to specify the argument and return value types.

`Function` is an abstract type. So for example `Vector{Function}` is like a `Vector{Any}`, or `Vector{Integer}`: Julia just canâ€™t infer the results. This can lead to type-instabilities, depending on how youâ€™re using it. FunctionWrappers.jl allows you to make this all better typed without having to make everything specific to each function. Parameterizing the type compiled a special version of the type dependent on the function you put in there.

Thereâ€™s a tradeoff: every new function you pass the parameterized version into will need to recompile, but specializing can have performance benefits (with this example, `x^2` will likely inline and avoid field access and function call costs). Pick your method depending on the problem.

What if I want to store a bunch of functions in a field, e.g. in an NTuple?
edit: I guess thatâ€™s FunctionWrappers.

Again, a tuple will explicitly type the whole thing, so that will work with a type parameter, but may be over-specializing. If you donâ€™t need that amount of specialization, then use FunctionWrappers.jl (which is actually a staging repo for thing that likely will be in Base â€śsoonâ€ť)

Is that true with NTuples?

An `NTuple` (by definition) contains homogeneous elements. Different functions are not the same type and you would therefore have the element type of the tuple would be `Function` which is not concrete so will not get specialization.

Ok thatâ€™s what I thought. How can I see the effect of this?

Dynamic dispatch when accessing the function stored in the tuple. Will be a performance hit if the function only does a little work.

Is it possible to see this with e.g. `@code_warntype`?

What I wrote before is actually wrong. `NTuple` or not does not matter so @ChrisRackauckas is right that the tuple will always specialize.

Right now, I have something like this (simplified):

``````immutable Liouvillian{N} <: AbstractParameterizedFunction{true}
fâ‚™::NTuple{N,Function}
end
``````

It does seem like this would not be specialized.

It will not be specialized in this sense:

``````julia> immutable F{N}
fs::NTuple{N, Function}
end

julia> f = F((x -> x, x->2*x))
F{2}((#1, #2))

ďżĽďżĽjulia> get_fs(f::F) = f.fs
get_fs (generic function with 1 method)
ďżĽ
julia> @code_warntype get_fs(f)
Variables:
#self#::#get_fs
f::F{2}

Body:
begin
return (Core.getfield)(f::F{2}, :fs)::Tuple{Function,Function}
end::Tuple{Function,Function}
``````
1 Like

Yes, that wonâ€™t specialize. But you can put a tuple in for:

``````immutable Liouvillian{F} <: AbstractParameterizedFunction{true}
fâ‚™::F
end
``````

and `fâ‚™` will specialize. You need to be really careful when doing this though. If `fâ‚™` is a tuple and `A` is the type, indexing with literals (`A.fâ‚™[3]`) is type-inferrable/stable. However, indexing with a variable (`i = 3; A.fâ‚™[i]`) is not. Julia is able to specifically handle indexing heterogeneous tuples with literals, but once the exact index is runtime information instead of compile-time information you lose the ability to infer.

``````julia> immutable F{N}
fs::N
end

julia> f = F((x->x,x->2x))
F{Tuple{##1#3,##2#4}}((#1, #2))

julia> get_fs(f::F) = f.fs
get_fs (generic function with 1 method)

julia> @code_warntype get_fs(f)
Variables:
#self#::#get_fs
f::F{Tuple{##1#3,##2#4}}

Body:
begin
return (#1, #2)
end::Tuple{##1#3,##2#4}

julia> get_fs2(f::F) = f.fs[2]
get_fs2 (generic function with 1 method)

julia> @code_warntype get_fs2(f)
Variables:
#self#::#get_fs2
f::F{Tuple{##1#3,##2#4}}

Body:
begin
return \$(QuoteNode(#2))
end::##2#4

julia> get_fsi(f::F,i::Int) = f.fs[i]
get_fsi (generic function with 1 method)

julia> @code_warntype get_fsi(f,2)
Variables:
#self#::#get_fsi
f::F{Tuple{##1#3,##2#4}}
i::Int64

Body:
begin
return (Base.getfield)((#1, #2), i::Int64)::Union{##1#3, ##2#4}
end::Union{##1#3, ##2#4}
``````

This may seem like magic at first, but just think carefully about â€śwhat information does the compiler have, and what can it possibly infer?â€ť Julia makes use of literals, but once you pass the integer it becomes runtime information, and thereâ€™s no way for the compiler to handle that.

1 Like