I only realized that functions are singleton types after writing the post above
.
Still, even after realizing that functions are singleton types I thought that there might be a certain reason to prefer passing functions as types: the Performance of captured variables issue. I thought that passing functions as types could enable writing more concise code with closures, because let isn’t necessary for type parameters. Long story short, turns out that let is also unnecessary for variables that happen to be of singleton type.
This is how I found this out:
fun_slow(f::F, g::G) where {F <: Function, G <: Function} =
x ->
let x::Float64 = x
function()
x = f(x)
g(x)
end
end
fun_ugly(f::F, g::G) where {F <: Function, G <: Function} =
let f = f, g = g
x ->
let f = f, g = g, x::Float64 = x
function()
x = f(x)
g(x)
end
end
end
(::Type{F})() where {F <: Function} = F.instance
fun_nice(::F, ::G) where {F <: Function, G <: Function} =
x ->
let x::Float64 = x
function()
x = F()(x)
G()(x)
end
end
My initial idea was that fun_slow would be very slow and the other two would be tied in speed, but fun_nice is obviously a lot nicer than fun_ugly. After benchmarking with BenchmarkTools.@benchmark it turned out that all three returned functions ran at the same speed.
Good to know that there’s one less case where let is necessary for closures
. Perhaps the Performance tips should be updated, if this behavior is something that can be counted on.