I don’t understand why the second way to construct a method (dispatched by ::Val{2}) is not allowed:
julia> abstract type MyFunc <: Function end
julia> struct MyF1 <: MyFunc end
julia> (f::MyFunc)(::Val{1}) = "method 1"
julia> MyF1()(Val(1))
"method 1"
julia> function (f::F)(::Val{2}) where {F<:MyFunc}
"method 2"
end
ERROR: function type in method definition is not a type
I agree the error message could be improved. Technically, the method type parameter (f::F)(::Val{2}) is not a parametric type like (f::(F where {F<:MyFunc}))(::Val{3}), which can’t share a where clause with arguments and is simplified to f::MyFunc (see methods). There’s not much reason to let the callable have or share method type parameters like arguments; the non-specialization heuristic that is overridden by method type parameters only applies to arguments, and restricting the callable and an argument to the same concrete type matching type parameters or concrete argument types only makes an unnecessary argument for recursion. I don’t see a reason why it can’t be done, either.
julia> function (f::F where {F<:MyFunc})(::Val{3})
"method 3"
end
julia> MyF1()(Val(3))
"method 3"
julia> function (f::F where {F<:MyFunc})(::Val{4}) # F will not be available within the function body
return F
end
julia> MyF1()(Val(4))
ERROR: UndefVarError: `F` not defined
See that this is still useful for dispatch, but not for parameter extraction. Although in this case one could use typeof(f) within the body.
Yeah. I understand the third approach. Unfortunately, what I showed is just a minimal demonstration. In my code, I actually do want to capture the type information F in the function body.
I can actually bypass this issue by enclose F in a parametric type. For instance:
julia> function (f::Type{F})(::Val{5}) where {F<:MyFunc}
"method 5"
end
julia> MyF1(Val(5))
"method 5"
However, practically, this is simply “method 2” with an extra step. So I’m just curious from a language design perspcetive why “method 2” is forbidden. More specifically, why is the F in a function definition form
function (f::F)(::Val{2}) where {F<:MyFunc}
"method 2"
end
I wasn’t really trying to share parameters between the callable object F and its arguments but wanting to capture its type information in the function body. In reality, I was trying to define a set of callable types that share similar traits but also slight differ from each other in a few cases when they are called. I did find a workaround:
julia> function (f::Type{F})(::Val{5}) where {F<:MyFunc}
"method 5"
end
julia> MyF1(Val(5))
"method 5"
I am still curious about the reason for original restriction (“method 2”) in the first place.
It’s just a type parameter, not a parametric type. The method’s where clause doesn’t actually belong to any of the type annotations, but even if we do incorporate it, the bare parameter does not make a UnionAll:
julia> (Type{F} where F<:Function)
Type{F} where F<:Function
julia> (Type{F} where F<:Function) |> typeof
UnionAll
julia> (F where F<:Function)
Function
julia> (F where F<:Function) |> typeof
DataType
In actuality, the method’s where clause belongs to the method signature’s tuple type:
julia> (f::MyType{F})(x::F) where F<:Function = 0
julia> methods(MyType(+))[1].sig
Tuple{MyType{F}, F} where F<:Function
And it’s possible to annotate bare parameters for arguments for such a type. It’s just not supported for callables, and I can’t find compelling reasons for or against it. (callable::T)(data::T) where T<:AbstractCallableData sounds like a neat bonus feature.
This changes the callable from the instances to the MyFunc subtypes, making a method that mingles with the constructors. I’d avoid that.
Could just do typeof(f) in method 1, it’s simple enough to do at compile-time.
I don’t know enough about the dispatch implementation to be able to give a definite answer. I think, however, that you’re asking for a very powerful feature: you’re really trying to define an infinite family of methods using the usual syntax, which is only able to define a single method at a time.
Parametric methods involving the callable aren’t that crazy, unless I’m missing the distinction being made between the unsupported method (::T)(x::T) where T<:MyType and:
julia> @which Number(2)
(::Type{T})(x::T) where T<:Number
This has been my approach to a similar problem in my use case. I haven’t found any issues with it so far, let me know if you guys know any. In summary I use @eval in a macro which gets around the function definition not knowing the parametric type of the function.