Imagine, I have a function that depends on some parameters, like step functions in DIfferentialEuations.jl. I would like to define these parameters once and avoid passing them as arguments at every function call. As a solution I can use function-like objects:

struct PFunction <: Function
func :: Function
p :: Tuple
end
function (pfunc::PFunction)(x...)
pfunc.func(x..., pfunc.p)
end
function foo(x, y, p)
a, b = p
return a * x + b * y
end
p1 = (1, 2)
pfoo1 = PFunction(foo, p1)
p2 = (3, 4)
pfoo2 = PFunction(foo, p2)
@show pfoo1(1, 1)
@show pfoo2(1, 1)

pfoo1(1, 1) = 3
pfoo2(1, 1) = 7

Thus, my goal is reached: instead of calling foo as foo(x, y, p), I can use just pfoo(x, y).

However, since the type of PFunction is not isbits type, I can not use such objects in CUDA kernels. Can you please suggest any solution that will allow me to obtain the same functions behavior inside CUDA kernels. I guess, I need to look towards metaprogramming, but I have no experience with macros so far.

julia> struct PFunction{T<:Tuple} <: Function
p::T
end
julia> function (pfunc::PFunction)(x,y)
foo(x, y, pfunc.p)
end
julia> function foo(x, y, p)
a, b = p
return a * x + b * y
end
foo (generic function with 1 method)
julia> p1 = (1, 2)
(1, 2)
julia> pfoo1 = PFunction(p1)
(::PFunction{Tuple{Int64,Int64}}) (generic function with 1 method)
julia> pfoo1(1,1)
3
julia> isbits(pfoo1)

Note, that the type-parameter of PFunction is needed to make it isbits, as Tuple is not a concrete type.

Oh, thank you very much! With your approach even the original code seems to work:

struct PFunction{F<:Function, T<:Tuple} <: Function
func :: F
p :: T
end
function (pfunc::PFunction)(x...)
pfunc.func(x..., pfunc.p)
end
function foo(x, y, p)
a, b = p
return a * x + b * y
end
p = (1, 2)
pfoo = PFunction(foo, p)
@show pfoo(1, 1)
@show isbits(pfoo)

pfoo(1, 1) = 3
isbits(pfoo) = true

Now I have to understand why type-parameters work this way. Looks a bit counterintuitive to me.