Now, since this can techincally already be computed at compile time, why not also make this function usable as a “type dispatch filter” function, for example
julia> positive(::T) where property(T) where T<:data = "dispatch on positive C only"
ERROR: syntax: invalid variable expression in "where"
Since property(T) is a “type function” it returns Bool on Type inputs, which means it is not much differnt from T<:data which is also a “type function” returning a Bool.
Therefore, it would make sense to be able to define more “type functions” for specific types, other than only <: for example.
This has been previously discussed
However, I’d like to have another fresh look at this:
You are encouraged to dispatch on computed (hopefully constant) results, i.e. traits but this should not be part of the dispatch itself since there’s a strong push against making the dispatch Turing complete, i.e. unpredictable (undecidable). IIRC, this doesn’t only cover allow running arbitrary code as part of dispatch but maybe some other more complex forms as well.
That said, more integrated syntax without changing the semantics is certainly possible if the distinction is made clear (e.g. your where property(T), spelled differently, could be made a check in the function body, if the syntax makes it clear that this is what’s happening)
Dispatch is to decide which method to call. In particular, in julia, it needs to decide whether a function is applicable and also which one is the most specific method. The requirement to decide on specifity means that the dispatch rule cannot be turing complete. For example, it’ll be impossible to tell, in general, if f(::T) where property1(T) is more specific than f(::T) where property2(T) in the cases where both matches. (The only safe solution is then to always throw an error in ambiguious case like this, which is what C++ does).
As shown in the example, this directly rules out running arbitrary code (no matter how pure it is) during dispatch. Extending the set of operation allowed is possible, as was done in 0.6, but callling arbitrary black box function is not.
The ability for the dispatch code to fully “understand” the dispatch rule also means that the compiler can do that too, even with partial information. This enables type inference to infer code even without full type information and in general gives inference an easier time doing its job… As an example, that’s why the following code can be fully inferred even though the input type to f is unknown.
julia> f(x) = 1
f (generic function with 1 method)
julia> f(x::Integer) = 2
f (generic function with 2 methods)
julia> function g(x)
f(x[])
end
g (generic function with 1 method)
julia> @code_warntype g(Ref{Integer}(1))
Body::Int64
2 1 ─ (Base.getfield)(x, :x) │╻╷ getindex
└── return 2 │
Note that the requirement I listed above only applies when you want to make garantee about certain aspect of the system, i.e. the decidability of the dispatch (and making sure you can make prediction based on partial type that is correct). It does not mean that you can’t run arbirary code and feed the result of that into dispatch. Since the code you put in there could be arbitrary and there’s in general no guarantee that the compiler can know what the input value is at compile time, you may not get guaranteed static dispatch at compile time, but the code should always run fine whether or not it’s optimized (and should be optimized most of the time if it’s well written and well used).
Traits is basically doing exactly this with tools around it for convinience and you can see this in the example from @Tamas_Papp above. The actual dispatch happens at _dosomething (presumably you can define one with Val{false}) and you can feed whatever you want as the first argument based on arbitrary computation you did in is_positive_trait.