Closed KlausC closed 4 years ago
But defining foo(x::Any)
is equivalent to foo(x)
, so I don't see the point. Can you show me the exact issues with AbstractArray
- IIRC that is an abstract base type, not an interface in the sense otf this package. At the other hand defining foo(x::Base.Bottom)
does not make sense, because there exists no x
, which would trigger its calling.
julia> foo(x::Base.Bottom) = 0
foo (generic function with 1 method)
julia> foo(1)
ERROR: MethodError: no method matching foo(::Int64)
Closest candidates are:
foo(::Union{}) at REPL[65]:1
Stacktrace:
[1] top-level scope at REPL[66]:1
[2] eval(::Module, ::Any) at ./boot.jl:331
[3] eval_user_input(::Any, ::REPL.REPLBackend) at /home/julia/julia/usr/share/julia/stdlib/v1.5/REPL/src/REPL.jl:132
[4] run_backend(::REPL.REPLBackend) at /home/crusius/.julia/dev/Revise/src/Revise.jl:1070
[5] top-level scope at none:1
julia> methods(foo)
# 1 method for generic function "foo":
[1] foo(x::Union{}) in Main at REPL[65]:1
julia> foo(x) = 1
foo (generic function with 2 methods)
julia> methods(foo)
# 2 methods for generic function "foo":
[1] foo(x::Union{}) in Main at REPL[65]:1
[2] foo(x) in Main at REPL[68]:1
julia> foo(x::Any) = 2
foo (generic function with 2 methods)
julia> methods(foo)
# 2 methods for generic function "foo":
[1] foo(x::Union{}) in Main at REPL[65]:1
[2] foo(x) in Main at REPL[70]:1
julia> foo(99)
2
Let's try a more concrete example.
@trait Friendly prefix Is,Not
@implement IsFriendly by play_with(other::Any)
Now, let's say you have two animal types:
struct Cat end
struct Mouse end
And, we believe that Cat is friendly:
@assign Cat with Friendly
If we would have to implement the play
function for cat, then we do:
play(me::Cat, other::Cat) = "little scratchy"
This implementation would not satisfy the interface because it does not take anything other than Cat
. The interface says that your method MUST accept Any
(not a subclass of Any
). Hence, you must provide an implementation that can cover the type that is being specified. To satisfy this contract, we have to do the following:
play(me::Cat, other::Any) = "little scratchy"
And that would be a bad choice because the other
argument could really be anything, not just animals.
More formally, functions signatures are contra-variant in this case, which is inverted from the case of calling a function. I wrote about the variance topic in my book Hands-on Design Patterns and Best Practices with Julia. Another good reference is Eli's blog about covariance and contravariance.
I am quite aware of the covariance-contravariance subject (I worked a while with Scala). Neverthless I cannot understand your conclusion, that the default value should be Bottom
and not Any
. Using Any
is exactly what the method definition does in the absence of an explicit argument type.
Maybe I am confused about what is the purpose of @implement
and interfaces. Currently I see only the check
function, which verifies with hasmethod
if a method with exactly the given argument types is defined. With other words, the check actually done is invariant.
What I would expect, would be like this:
If I specify @implement IsFriendly by play_with(other::Mammal)
, then I can call play_with(me, other)
if only other isa Mammal
.
But I do not expect necessarily an implementation play_with(..., ::Mammal)
; there could be play_with(..., ::Animal)
with Cat <: Mammal <: Animal
, which is currently not recognized by check
.
Maybe I understand now better your argument. If we specify in the interface @implement IsFriendly by be_empathic_with(..., object::Base.Bottom)
the interface would be met, if there was a function be_empathic_with(..., x::Whatever)
for any type , because Bottom <: Whatever
. So I agree, if we had this improved hasmethod
, Bottom
would be a potential candidate for a default value.
Right. Just want to note that the hasmethod
function already handles variance properly. For example:
julia> boo(::Any) = 1
boo (generic function with 1 method)
julia> hasmethod(boo, (Any,))
true
julia> hasmethod(boo, (Int,))
true
In this case, the specification of my interface may be to accept an Int
argument. But since I have implemented the function with a broader type, Any
, the hasmethod
figured that a call with an Int
argument can be dispatched to my function.
Ok, good. I must have overlooked something. Actually I tested with Base.Bottom
:
julia> Base.Bottom <: Int
true
julia> struct Duck end
julia> Base.Bottom <: Int
true
julia> fly(::Duck, ::Int) = 1
fly (generic function with 1 method)
julia> hasmethod(fly, Tuple{Duck,Base.Bottom})
false
!!!
julia> fly(::Duck, ::Real) = 2
fly (generic function with 2 methods)
julia> hasmethod(fly, Tuple{Duck,Base.Bottom})
true
It looks like hasmethod
does not work as expected, if the argument type is concrete.
Just found this issue related to the Base.Bottom <: Int
topic: https://github.com/JuliaLang/julia/issues/30808
The argument requires to specify an argument type for each argument - could default to Any
Yes, but I am unsure if Any is the right default. When I tried to use it with the AbstractArray interface, which uses duck typing in the interface, I figured that I need to use Base.Bottom instead. See the LinearIndexing trait example. If we want to impose a default then I think Bottom is more appropriate because otherwise the implementer must define the function that accepts explicitly Any.