# Function as Type Parameter

Consider a struct, that takes a function as a type parameter:

``````struct Foo{F} end

function call(foo::Foo{F}, x) where {F}
F(x)
end
``````

Seems great at first, as can be seen by

``````# This works
r = Base.RefValue(3)
foo = Foo{x -> r[] * x}()
call(foo, 10)
``````

Performance is also way better than having the function as a field.

But I can’t use this struct in functions if it wraps a closure wrapping a mutable type:

``````function usesfoo(d)
foo = Foo{x -> x + d[]}()
call(foo, 2)
end

usesfoo(Base.RefValue(3))
``````

How can I work around it, while keeping the good performance (lazyarrays.jl · GitHub)?

Maybe a callable struct with the function as a field.

3 Likes

That was my original implementation, but then I have to call it via its function pointer. In my test case it was about 40x slower (lazyarrays.jl · GitHub)

I specifically want F to be statically compiled into Foo

It looks like your implementation is not quite what @lmiq proposes. Mind that field type for `f` is `F`, not `Function`.
If the implementation is redone as

``````mutable struct LazyFunctionArray{F,T,N} <: AbstractArray{T,N}
const f::F                   # Julia 1.8 const field syntax for convenience
const size::NTuple{N,Int}
ncalls::Int
end

const LazyFunctionVector{F,T} = LazyFunctionArray{F,T,1}
const LazyFunctionMatrix{F,T} = LazyFunctionArray{F,T,2}

function LazyFunctionArray(T::Type, f::F, dims::Vararg{Int,N}) where {N,F<:Function}
LazyFunctionArray{F,T,N}(f, dims, 0)
end
...
``````

then the difference between `benchmark_eval_1()` and `benchmark_eval_2()` vanishes.

1 Like

Thank you, this exactly what I need. Here’s the final code for future reference:

``````# Quick implementation of a lazy Array. Yes, it's really *that* simple.
mutable struct LazyFunctionArray{F<:Function,T,N} <: AbstractArray{T,N}
const f::F                   # Julia 1.8 const field syntax for convenience
const size::NTuple{N,Int}
ncalls::Int
end
const LazyFunctionVector{F,T} = LazyFunctionArray{F,T,1}
const LazyFunctionMatrix{F,T} = LazyFunctionArray{F,T,2}

function LazyFunctionArray(T::Type, f::F, dims::Vararg{Int,N}) where {F<:Function,N}
LazyFunctionArray{F,T,N}(f, dims, 0)
end

function LazyFunctionArray(f::F, dims::Vararg{Int,N}) where {F<:Function,N}
LazyFunctionArray(Float64, f, dims...)
end

function LazyFunctionVector(T::Type, f::F, n::Int) where {F<:Function}
LazyFunctionVector{T,F}(f, (n,), 0)
end

function LazyFunctionVector(f::F, n::Int) where {F<:Function}
LazyFunctionVector(Float64, f, n)
end

function LazyFunctionMatrix(T::Type, f::F, n::Int, m::Int) where {F<:Function}
LazyFunctionMatrix{F,T}(f, (n, m), 0)
end

function LazyFunctionMatrix(f::F, n::Int, m::Int) where {F<:Function}
LazyFunctionMatrix(Float64, f, n, m)
end

function Base.size(A::LazyFunctionArray)
A.size
end

function Base.getindex(A::LazyFunctionArray{F,T,1,}, i::Int) where {F<:Function,T}
A.ncalls += 1
A.f(i)
end

function Base.getindex(A::LazyFunctionArray{F,T,N}, I::Vararg{Int,N}) where {F<:Function,T,N}
A.ncalls += 1
A.f(I...)
end

function benchmark_eval()
A = LazyFunctionArray((x, y) -> x + y, 1000, 1000)
m = Matrix(A)
@benchmark m = Matrix(\$A)
end

benchmark_eval()
``````