# Need help with resolving method ambiguities

Just started to experiment with implementing a proxy type, but I failed very early:

``````module Proxies

export AbstractProxy, Proxy

abstract type AbstractProxy end

struct Proxy{TInner} <: AbstractProxy
inner::TInner
Proxy{TInner}(inner) where TInner = new{TInner}(inner)
Proxy(inner) = begin
p = Proxy{typeof(inner)}(inner)
return p
end
end

inner(p) = p.inner

Base.convert(targettype::Type{<:T}, p::Proxy{TInner}) where {T, TInner} = begin
return convert(targettype, inner(p))
end

end # module
``````
``````using Proxies
using Test

@testset "Proxy creation" begin
p = Proxy(42)
@test p isa Proxy
@test Proxies.inner(p) == 42
end

@testset "transparent proxing" begin
@test convert(Int, Proxy(42)) isa Int
@test convert(Int, Proxy(42.0)) isa Int
@test Proxy(42) == 42
@test_broken Proxy(42) + 42 == 84
end
``````
``````Test Summary:  | Pass  Total
Proxy creation |    2      2
Test Summary:       | Pass  Total
transparent proxing |    2      2
ERROR: LoadError: MethodError: convert(::Type{Any}, ::Proxy{Int64}) is ambiguous. Candidates:
convert(::Type{Any}, x) in Base at essentials.jl:170
convert(targettype::Type{var"#s13"} where var"#s13"<:T, p::Proxy{TInner}) where {T, TInner} in Proxies at /home/krisztian/projects/Proxies.jl/src/Proxies.jl:19
Possible fix, define
convert(::Type{Any}, ::Proxy{TInner}) where TInner
Stacktrace:
[1] Base.RefValue{Any}(::Proxy{Int64}) at ./refvalue.jl:8
[2] Ref{Any}(::Proxy{Int64}) at ./refpointer.jl:96
[3] inferencebarrier(::Any) at ./essentials.jl:718
[4] show_default at ./show.jl:389 [inlined]
[5] show(::IOContext{Base.GenericIOBuffer{Array{UInt8,1}}}, ::Any) at ./show.jl:384
[6] show_delim_array(::IOContext{Base.GenericIOBuffer{Array{UInt8,1}}}, ::Tuple{DataType,Proxy{Int64}}, ::Char, ::Char, ::Char, ::Bool, ::Int64, ::Int64) at ./show.jl:776
[7] show_delim_array at ./show.jl:761 [inlined]
[8] show(::IOContext{Base.GenericIOBuffer{Array{UInt8,1}}}, ::Tuple{DataType,Proxy{Int64}}) at ./show.jl:794
[9] _show_default(::IOContext{Base.GenericIOBuffer{Array{UInt8,1}}}, ::Any) at ./show.jl:406
[10] show_default at ./show.jl:389 [inlined]
[11] show(::IOContext{Base.GenericIOBuffer{Array{UInt8,1}}}, ::Any) at ./show.jl:384
[12] sprint(::Function, ::MethodError; context::Pair{Symbol,Bool}, sizehint::Int64) at ./strings/io.jl:103
[13] Test.Error(::Any, ::Any, ::Any, ::Any, ::Any) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Test/src/Test.jl:162
[14] top-level scope at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Test/src/Test.jl:1121
[15] top-level scope at /home/krisztian/projects/Proxies.jl/test/runtests.jl:11
[16] include(::String) at ./client.jl:457
[17] top-level scope at none:6
in expression starting at /home/krisztian/projects/Proxies.jl/test/runtests.jl:10
caused by [exception 1]
MethodError: convert(::Type{Any}, ::Proxy{Int64}) is ambiguous. Candidates:
convert(::Type{Any}, x) in Base at essentials.jl:170
convert(targettype::Type{var"#s13"} where var"#s13"<:T, p::Proxy{TInner}) where {T, TInner} in Proxies at /home/krisztian/projects/Proxies.jl/src/Proxies.jl:19
Possible fix, define
convert(::Type{Any}, ::Proxy{TInner}) where TInner
Stacktrace:
[1] Base.RefValue{Any}(::Proxy{Int64}) at ./refvalue.jl:8
[2] Ref{Any}(::Proxy{Int64}) at ./refpointer.jl:96
[3] inferencebarrier(::Any) at ./essentials.jl:718
[4] show_default at ./show.jl:389 [inlined]
[5] show(::IOContext{Base.GenericIOBuffer{Array{UInt8,1}}}, ::Any) at ./show.jl:384
[6] show_delim_array(::IOContext{Base.GenericIOBuffer{Array{UInt8,1}}}, ::Tuple{DataType,Proxy{Int64}}, ::Char, ::Char, ::Char, ::Bool, ::Int64, ::Int64) at ./show.jl:776
[7] show_delim_array at ./show.jl:761 [inlined]
[8] show(::IOContext{Base.GenericIOBuffer{Array{UInt8,1}}}, ::Tuple{DataType,Proxy{Int64}}) at ./show.jl:794
[9] _show_default(::IOContext{Base.GenericIOBuffer{Array{UInt8,1}}}, ::Any) at ./show.jl:406
[10] show_default at ./show.jl:389 [inlined]
[11] show(::IOContext{Base.GenericIOBuffer{Array{UInt8,1}}}, ::Any) at ./show.jl:384
[12] sprint(::Function, ::MethodError; context::Pair{Symbol,Bool}, sizehint::Int64) at ./strings/io.jl:103
[13] Test.Error(::Any, ::Any, ::Any, ::Any, ::Any) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Test/src/Test.jl:162
[14] do_test(::Test.ExecutionResult, ::Any) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Test/src/Test.jl:518
[15] top-level scope at /home/krisztian/projects/Proxies.jl/test/runtests.jl:13
[16] top-level scope at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.5/Test/src/Test.jl:1115
[17] top-level scope at /home/krisztian/projects/Proxies.jl/test/runtests.jl:11
[18] include(::String) at ./client.jl:457
[19] top-level scope at none:6
ERROR: Package Proxies errored during testing
``````

I would really appreciate any help on this!

In your original code, I get the following error:

``````julia> p = Proxy(42)
Error showing value of type Proxy{Int64}:
ERROR: MethodError: convert(::Type{Any}, ::Proxy{Int64}) is ambiguous. Candidates:
convert(::Type{Any}, x) in Base at essentials.jl:170
convert(targettype::Type{var"#s2"} where var"#s2"<:T, p::Proxy{TInner}) where {T, TInner} in Main at REPL[4]:1
Possible fix, define
convert(::Type{Any}, ::Proxy{TInner}) where TInner
``````

The last line suggests an ambiguity fix:

``````function Base.convert(::Type{Any}, ::Proxy{TInner}) where TInner
convert(Any, inner(p))
end
``````

That gets rid of the ambiguities, but then you have more methods to implement to get your tests passing.

1 Like

Thank you, not clear how I have missed that!

1 Like

Just as a note, it is quite questionable to have a type that converts to absolutely any other type. Conversions should usually result in something that is very similar to the original thing (`1` -> `1.0`) and it is unclear if you can have a type such that this is true for all the types it wraps.

It might be better to have a designated `unwrap` method or something along those lines.

5 Likes

Thanks for noting this! My understanding of conversion and promotion is not perfect yet, so possibly even the goal of this method could be improved. With

``````Base.convert(targettype::Type{<:T}, p::Proxy{TInner}) where {T, TInner} = begin
return convert(targettype, inner(p))
end
``````

I have tried to say in Julia that "If a conversion is available from `TInner` to `T`, then here I provide one from `Proxy{TInner}` to `T`"

On the topic of

and

The method:

``````function Base.convert(targettype::Type{<:T}, p::Proxy{TInner}) where {T, TInner}
return convert(targettype, inner(p))
end
``````

causes `1934` invalidationsâ€¦

3 Likes

Great, better than the winning `convert` in the non-pirating category!

I have thought that implementing a proxy type (similar to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy ) will be relatively simple, so a good way to learn more about how types are working. Never thought that it will be performant when implemented naively, because a lot of evaling seems to be needed. (Not sure if non-naive implementation is possible though)

3 Likes

My instinct is that you canâ€™t prevent those and you donâ€™t want to.
I am pretty sure it would require changes to how the things you invalidated were written.
Or to the code that calls them.

Invalidations are not, as a rule, a problem for package authors to worry about.
Itâ€™s a problem the compiler and standard library folk to worry about.

3 Likes

Reading through that link, I think a naive implementation (even a not very naive oneâ€¦) is not possible in julia. You would need a way to replace arbitrary function calls on the type with different ones (at runtime, without macros and so on presumably, or else you severely limit the usability of Proxy). The way javascript is interpretted is different enough from how julia is compiled that there canâ€™t be an exact equivalent to `Proxy` without call overloading on `::Function` or `::Any`

With a macro you could do:

``````@proxy p let getproperty = blah, setproperty! = blah

p.x = p.x + p.y # can be translated by the macro to whatever

end
``````

but doesnâ€™t really fill the same niche as `Proxy`. Normally you would just define a new type or a new method, depending on what behavior you need changedâ€¦