hasmethod error
aminya opened this issue · 7 comments
I am trying to define this trait but it gives me errors.
@trait StringConvertible{T} begin
(hasmethod) :: (convert, Tuple{String, T}) => true
end
ERROR: error in method definition: function Base.hasmethod must be explicitly imported to be extended
Stacktrace:
[1] top-level scope at none:0
[2] top-level scope at none:1
Trying the following changes the error:
@trait StringConvertible{T} begin
(Base.hasmethod) :: (convert, Tuple{String, T}) => true
end
ERROR: LoadError: MethodError: no method matching extract_method_interface(::Expr, ::Array{Expr,1}, ::Bool, ::Array{Any,1})
Closest candidates are:
extract_method_interface(::Symbol, ::AbstractArray, ::Any, ::AbstractArray) at C:\Users\yahyaaba\.julia\packages\CanonicalTraits\IA6yQ\src\Utils.jl:87
Stacktrace:
[1] ##CanonicalTraits 146#400#51 at .\none:0 [inlined]
[2] ##CanonicalTraits 144#398#50 at .\none:0 [inlined]
[3] ##CanonicalTraits 137#391#49 at .\none:0 [inlined]
[4] ##CanonicalTraits 135#389#48 at .\none:0 [inlined]
[5] extract_method(::Expr) at C:\Users\yahyaaba\.julia\packages\CanonicalTraits\IA6yQ\src\Utils.jl:99
[6] iterate at .\generator.jl:47 [inlined]
[7] collect_to!(::Array{LineNumberNode,1}, ::Base.Generator{SubArray{Any,1,Array{Any,1},Tuple{UnitRange{Int64}},true},typeof(CanonicalTraits.extract_method)}, ::Int64, ::Tuple{Base.OneTo{Int64},Int64}) at .\array.jl:711
[8] collect_to_with_first!(::Array{LineNumberNode,1}, ::LineNumberNode, ::Base.Generator{SubArray{Any,1,Array{Any,1},Tuple{UnitRange{Int64}},true},typeof(CanonicalTraits.extract_method)}, ::Tuple{Base.OneTo{Int64},Int64}) at .\array.jl:689
[9] _collect(::SubArray{Any,1,Array{Any,1},Tuple{UnitRange{Int64}},true}, ::Base.Generator{SubArray{Any,1,Array{Any,1},Tuple{UnitRange{Int64}},true},typeof(CanonicalTraits.extract_method)}, ::Base.EltypeUnknown,
::Base.HasShape{1}) at .\array.jl:683
[10] collect_similar at .\array.jl:607 [inlined]
[11] map at .\abstractarray.jl:2072 [inlined]
[12] ##CanonicalTraits 406#660#110 at C:\Users\yahyaaba\.julia\packages\CanonicalTraits\IA6yQ\src\Typeclasses.jl:101 [inlined]
[13] (::CanonicalTraits.var"##CanonicalTraits 406#660#110"{Symbol,SubArray{Any,1,Array{Any,1},Tuple{UnitRange{Int64}},true},LineNumberNode,Array{Pair{Symbol,Expr},1},Array{Expr,1}})(::Array{Any,1}) at .\none:0
[14] ##CanonicalTraits 404#658#109 at .\none:0 [inlined]
[15] ##CanonicalTraits 416#670#108 at C:\Users\yahyaaba\.julia\packages\CanonicalTraits\IA6yQ\src\Typeclasses.jl:96 [inlined]
[16] (::CanonicalTraits.var"##CanonicalTraits 414#668#107"{LineNumberNode,Expr,Array{Pair{Symbol,Expr},1},Array{Expr,1}})(::Expr) at .\none:0 [17] trait(::LineNumberNode, ::Any, ::Any) at C:\Users\yahyaaba\.julia\packages\CanonicalTraits\IA6yQ\src\Typeclasses.jl:96
[18] @trait(::LineNumberNode, ::Module, ::Any, ::Any) at C:\Users\yahyaaba\.julia\packages\CanonicalTraits\IA6yQ\src\Typeclasses.jl:151
in expression starting at none:1
Hello, this is intentional because overloading any existing julia function can cause unexpected behaviour.
CanonicalTraits will take over some methods definition functionalities overlapped with mulitiple dispatch.
Although you can try import Base: hasmethod
, it's still not encouraged if you don't understand the underlying of CanonicalTraits.
Besides, the definition of multiple-ary trait method should be
@trait StringConvertible{T} begin
yourhasmethod :: [convert, Tuple{String, T}] => true
end
[a, b]
instead of (a, b)
.
I don't want to overload hasmethod
. I just want to define a trait that works based on what hasmethod
gives.
@aminya this is what you want:
@trait StringConvertible{T} begin
has_tostr_method :: Type{T} => Bool
has_tostr_method(x) = hasmethod(convert, Tuple{Type{String}, x}) # default method
end
@implement StringConvertible{T} where T # implement for any types, it'll use default method
Base.convert(::Type{String}, x::Symbol) = string(x)
julia> has_tostr_method(Symbol)
true
julia> has_tostr_method(Int)
false
Thanks! This works. It would be cool to add this example to the documentation. This is one of the most used cases for traits.
However, this seems slower than the original hasmethod
.
julia> t = Int64
julia> @btime has_tostr_method($t)
21.700 μs (20 allocations: 864 bytes)
julia> @btime hasmethod(convert, Tuple{String, $t})
13.499 μs (2 allocations: 64 bytes)
false
Thanks! This works. It would be cool to add this example to the documentation. This is one of the most used cases for traits.
Welcome! I'm always not sure which part people would get interested in, and if you want maybe you could add a PR to register this example in README or documentations.
Although this seems slower than the original hasmethod.
Yes, in some cases it can be, and this is one example.
When performance hit occurs, it is usually really really confusing to me, because the generated code is actually equivalent and CanonicalTriats shall add no overhead:
julia> @code_llvm has_tostr_method(t)
; @ none within `has_tostr_method'
; Function Attrs: uwtable
define i8 @julia_has_tostr_method_17849(%jl_value_t addrspace(10)*) #0 {
top:
; ┌ @ REPL[11]:2 within `##has_tostr_method#277#6'
; │┌ @ reflection.jl:1245 within `hasmethod'
; ││┌ @ reflection.jl:1247 within `#hasmethod#25'
%1 = call %jl_value_t addrspace(10)* inttoptr (i64 1720400272 to %jl_value_t addrspace(10)* (%jl_value_t addrspace(10)*, i64)*)(%jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 852995920 to %jl_value_t*) to %jl_value_t addrspace(10)*), i64 -1)
%2 = addrspacecast %jl_value_t addrspace(10)* %1 to %jl_value_t addrspace(12)*
%3 = icmp ne %jl_value_t addrspace(12)* %2, addrspacecast (%jl_value_t* inttoptr (i64 162493776 to %jl_value_t*) to %jl_value_t addrspace(12)*)
%4 = zext i1 %3 to i8
; └└└
ret i8 %4
}
julia> @code_llvm hasmethod(convert, Tuple{String, t})
; @ reflection.jl:1245 within `hasmethod'
; Function Attrs: uwtable
define i8 @julia_hasmethod_17851(%jl_value_t addrspace(10)*) #0 {
top:
; ┌ @ reflection.jl:1247 within `#hasmethod#25'
%1 = call %jl_value_t addrspace(10)* inttoptr (i64 1720400272 to %jl_value_t addrspace(10)* (%jl_value_t addrspace(10)*, i64)*)(%jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 368565152 to %jl_value_t*) to %jl_value_t addrspace(10)*), i64 -1)
%2 = addrspacecast %jl_value_t addrspace(10)* %1 to %jl_value_t addrspace(12)*
%3 = icmp ne %jl_value_t addrspace(12)* %2, addrspacecast (%jl_value_t* inttoptr (i64 162493776 to %jl_value_t*) to %jl_value_t addrspace(12)*)
%4 = zext i1 %3 to i8
; └
ret i8 %4
}
I don't think adding more source code position metadata will affect the performance..
I thought CanonicalTraits.jl is able to do something similar to what Tricks.jl does. Because for Tricks, static_hasmethod takes almost 0 time.
@aminya I know that trick, and even without the julia edge system.
If your program is much slower than getting the world age, you can use this trick.
I did post an implementation in Julia CN discourse: https://discourse.juliacn.com/t/topic/2347
This trick is also applied to CanonicalTraits.jl, in @implement!
, for constant method lookup. However Base.hasmethod
itself is not a CanonicalTraits thing so it wouldn't be handled that way, because I cannot know an outer function is pure or not.