Dispatching on function types?

Motivating problem:

julia> struct MappedElement{F <: Function,T <: Real} <: Real 
           x::T
           fx::T
           MappedElement{F}(x::T, fx::T) where {F,T} = new{F,T}(x, fx)
           MappedElement{F}(x, fx) where {F} = MappedElement{F}(promote(x, fx)...)
       end

julia> MappedElement(f::F, x) where {F} = MappedElement{F}(x, f(x))
MappedElement

julia> (::typeof(F))(x::MappedElement{typeof(F)}) where {F} = x.fx # errors
ERROR: TypeError: in MappedElement, in F, expected F<:Function, got Type{TypeVar}
Stacktrace:
 [1] top-level scope at REPL[3]:1

julia> (::typeof(F))(x::MappedElement{F}) where {F} = x.fx # does not error

julia> meexp = MappedElement(exp, 2.3);

julia> exp(meexp) # errors
ERROR: MethodError: no method matching exp(::MappedElement{typeof(exp),Float64})
Closest candidates are:
  exp(::BigFloat) at mpfr.jl:618
  exp(::Missing) at math.jl:1138
  exp(::Complex{Float16}) at math.jl:1086
  ...
Stacktrace:
 [1] top-level scope at REPL[6]:1

julia> (::typeof(log))(x::MappedElement{typeof(log)}) = x.fx

julia> melog = MappedElement(log, 2.3); # works

julia> log(melog) #works
0.8329091229351039

The idea would be to have a number that caches the result of some function applied to it.

The definition:

 (::typeof(F))(x::MappedElement{F}) where {F} = x.fx # does not error

looks wrong, and also fails to actually work, but evaluating it does not error, unlike

 (::typeof(F))(x::MappedElement{typeof(F)}) where {F} = x.fx

For what it’s worth, lowered forms of the expressions:

julia> Meta.@lower (::typeof(log))(x::MappedElement{typeof(log)}) = x.fx
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope'
1 ─ %1 = typeof(log)
│   %2 = typeof(log)
│   %3 = Core.apply_type(MappedElement, %2)
│   %4 = Core.svec(%1, %3)
│   %5 = Core.svec()
│   %6 = Core.svec(%4, %5)
│        $(Expr(:method, false, :(%6), CodeInfo(quote
    Base.getproperty(x, :fx)
    return %1
end)))
└──      return
))))

julia> Meta.@lower (::typeof(F))(x::MappedElement{::typeof(F)}) where {F} = x.fx
:($(Expr(:error, "invalid \"::\" syntax")))

julia> Meta.@lower (::typeof(F))(x::MappedElement{typeof(F)}) where {F} = x.fx
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope'
1 ─ %1 = Core.TypeVar(:F)
│   %2 = typeof(%1)
│   %3 = typeof(%1)
│   %4 = Core.apply_type(MappedElement, %3)
│   %5 = Core.svec(%2, %4)
│   %6 = Core.svec(%1)
│   %7 = Core.svec(%5, %6)
│        $(Expr(:method, false, :(%7), CodeInfo(quote
    Base.getproperty(x, :fx)
    return %1
end)))
└──      return
))))

julia> (::typeof(F))(x::MappedElement{typeof(F)}) where {F} = x.fx
ERROR: TypeError: in MappedElement, in F, expected F<:Function, got Type{TypeVar}
Stacktrace:
 [1] top-level scope at REPL[19]:1

julia> Meta.@lower (::typeof(F))(x::MappedElement{F}) where {F} = x.fx
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope'
1 ─ %1 = Core.TypeVar(:F)
│   %2 = typeof(%1)
│   %3 = Core.apply_type(MappedElement, %1)
│   %4 = Core.svec(%2, %3)
│   %5 = Core.svec(%1)
│   %6 = Core.svec(%4, %5)
│        $(Expr(:method, false, :(%6), CodeInfo(quote
    Base.getproperty(x, :fx)
    return %1
end)))
└──      return
))))

In the case where I write ::MappedElement{typeof(F}}, the code contains

1 ─ %1 = Core.TypeVar(:F)
│   %3 = typeof(%1)
│   %4 = Core.apply_type(MappedElement, %3)

Removing the typeof eliminates the %3. The non-parameteric typeof(log) version directly Core.apply_types using typeof(log).

To overload calls to functions passed a function, I cannot use typeof:
Base.broadcasted(::F, x::MappedElement{F}) where {F} = x.fx
But this does not work for defining a call:

julia> (::F)(x::MappedElement{F}) where {F} = x.fx
ERROR: function type in method definition is not a type
Stacktrace:
 [1] top-level scope at REPL[27]:1

Any ideas?

The concrete version of this works:

meexp = MappedElement(exp, 2.3);
Base.exp(x::MappedElement{typeof(exp)}) = x.fx
exp(meexp) # works

so I think this is a bug (or missing feature). I would recommend opening an issue.