Extended method not being seen by original module

I’m trying to implement a custom linear operator in for the LowRankApprox package, and some of my method definitions are not interacting with the methods in that package; e.g.

julia> ishermitian(A::CovarianceOperator) = true
ishermitian (generic function with 2 methods)

julia> A = CovarianceOperator(X)
CovarianceOperator{Float64}(1000, #1, Nullable{Array{Float64,N} where N}())

julia> ishermitian(A)
true

julia> pheigfact(A) #this is a function defined by LowRankApprox
ERROR: MethodError: no method matching ishermitian(::CovarianceOperator{Float64})

I expect that this issue is due to my own incomplete knowledge of the Julia module system or similar, so if anybody could set me straight, I would really appreciate it

1 Like

You probably need to import the method before extending it, otherwise you are overriding it (you can see it has only two methods):

import LowRankApprox: ishermitian
ishermitian(A::CovarianceOperator) = true
1 Like

Over time I have learned the lesson that when extending methods from other packages it is usually better to do this extension explicitly rather than using import. For example @jonathanBieler’s suggestion should work just fine, but you can also do

LowRankApprox.ishermitian(A::CovarianceOperator) = true

This will make it clear to anyone looking at your source code that ishermitian comes from LowRankkApprox, whereas the import may have been easy to miss, depending on where you put it. I’ve caused myself some embarrassing errors by changing a method I hadn’t realized was being imported (as I recall I did this with Base.write, so you can imagine the hilarity that ensued).

4 Likes

Ok, that seems to have solved the problem. I’m still a little confused though, since in the original case I had defined a method ishermitian, on CovarianceOperator - even if it overrode the original definition, shouldn’t it still work since presumably pheigfact only cares about getting ishermitian(::CovarianceOperator)? (when I say shouldn’t, clearly that is my mistake!)

Without an import LowRankApprox: ishermetian (or without explicitly defining your method as LowRankApprox.ishermetian(...) as @ExpandingMan suggests), you are creating a completely new function. The fact that that function shares its name with a function from LowRankApprox is completely coincidental and has no effect whatsoever. When someone (like, say, a function in LowRankApprox) calls LowRankApprox.ishermetian, the existence of your personal ishermetian function is of no consequence at all.

If you do have the import (or explicit definition of LowRankApprox.ishermetian), then you are actually extending the same function. So any calls to LowRankApprox.ishermetian (e.g. any calls from within LowRankApprox) will see your new method and choose it as appropriate.

I’d also second @ExpandingMan’s suggestion, as I find that explicit overloading is easier to reason about and less subject to non-local effects (in this case, the presence or absence of an import somewhere else in the module).

5 Likes

Thanks for the explanation - if I could ask one more style question; should this explicit overloading recommendation extend to functions in Base?

I would say especially functions in Base. The two exceptions I can think of is if you are extending a “interface” package which exists for the purpose of you adding methods to it in other packages and perhaps in some metaprogramming situations where omitting the original module might simplify the code or make it more portable.

You just need to be a little bit careful you are not overriding existing methods, but extending the function with a new type you defined; do Base.length(x::SoccerField) = 110m but not Base.length(x::Vector) = 110m (yes the example is maybe not the best because SoccerField is not a collection).