Overloading base operator from module

I would like to define a custom matrix-vector product for a type which I have declared in a module:

module JulHDG

export HollowSparseMatrix

  immutable HollowSparseMatrix
    bigA::SparseMatrixCSC{Float64, Int64}
    keepRows::Vector{Int64}
  end

  function *(A::HollowSparseMatrix, v::Vector)
    b = copy(v)
    for i=A.keepRows
      b[i] = dot(A.bigA[i, :], v[keepRows])
    end
    return b
  end

I can’t seem to make this work. When I try a test multiplication using the above, I get:

WARNING: both JulHDG and Base export "*"; uses of it in module Main must be qualified
ERROR: UndefVarError: * not defined

What is the correct way to go about this? I couldn’t find anything in the type section of the Julia docs.

To extend any functions including operators, you need to import it first

1 Like

tldr: add import Base: * inside your module.

Long version:
Julia already has a function called * (which does various kinds of multiplication) defined in Base, (Base is the set of functions that Julia ships with).
Calling function *(...) creates a new function called *. That doesn’t change or delete the function that Julia already has, but it does create an ambiguity: when you call *(x, y), (or equivalently, x * y), do you want the function * in Base, or the one in your module? That’s what the error says: “uses of it in module Main must be qualified” means: "you will now have to say Base.*(x, y) or JulHDG.*(x, y) every time you do multiplication. You don’t want that.

But you don’t want to have to choose. You want the ‘correct’ version of * to be automatically chosen for a particular input type, right? That’s the whole promise of Julia. To do that, you need to tell Julia that you’re not defining a brand-new function called *, but just adding some new behavior to the existing *. Importing * from Base tells Julia exactly that.

7 Likes

You can also qualify Base functions, including operators:
function Base.:*(A::HollowSparseMatrix, v::Vector)
The relevant section in Julia docs is Modules.

4 Likes

Personally, I now prefer qualifying with module names to importing. Makes it more readable, as I can resolve information just reading the function definition, instead of finding the imports which are usually in another file.

3 Likes

I think it makes it much less readable, because how am I supposed to know all of the functions you overloaded? In most packages, you just read the first few lines of the main file of the package (the one which matches the package name), but you’d have me search through the whole package for this information. Imports on top, abstract types, include files, exports. That’s a clear main file and you can pretty much read the whole user-API from that without going to another file. In that sense, silently overloading in some obscure file is the antithesis of readability (especially since it doesn’t follow conventions. Following conventions makes things easier to read/understand too!).

I agree that there are advantages and disadvantages to both approaches. The arguments I found convincing are here.

When writing a library/package, I use import. When writing an application, I qualify with module’s name.

3 Likes