How to make method globally accessible (for interface/protocols/polymorphic types) and avoid "per-module" extension?

Hello, I am new to Julia and it’s namespace system. I also don’t know the correct terminology for what I am trying to do (so apologies in advance).

I am trying to see how to one could write “polymorphic” types without having to extend the methods to the namespaces that will use those types. I know that “polymorphism” doesn’t make sense in Julia, but in Python, lets say there some code that takes in an object and does something with that object. I don’t know what the object is, but I know I can do something by calling the do_something() method:

def foo(x):
    x.do_something()

And I have to do is ensure that x has a do_something() method.

In Julia, I can get the same behavior, but only if I extend an existing do_something() method in ALL modules that calls this “type-defined” (for lack of a better word) method. Example:

module Test # Made by Programmer 1
  function do_something end # User-must define this method
  function foo(x)
    do_something(x) # Use user-defined method
  end
end

module TestB # Made by Programmer 2
  function do_something end # User-must define this method
  function foo(x)
    do_something(x) # Use user-defined method
  end
end

module Test2  # Made by Programmer 3 
  import ..Test

  struct MyType
    val
  end

  function do_something(x::MyType) # User is defining do_something (how to do this globally?)
    print(x.val)
  end
end

# Written by Programmer 4

# do_something implementation must be extended for EACH module expecting an implementation of do_something.
# How can I avoid the need to do per-module extension (next 7 lines) and instead just extend so that all modules can see "do_something(x::MyType)" without manual extension?
# Extending in Base doesn't work since "do_something" is not defined in that namespace.
function Test.do_something(x::Test2.MyType) 
  print(x.val)
end

function TestB.do_something(x::Test2.MyType) 
  print(x.val)
end

Test.foo(Test2.MyType("Hello World!\n"))
TestB.foo(Test2.MyType("Hello Universe!\n"))

How can I make the do_something function available globally, so that I don’t have to extend the function for multiple namespaces that might use it? Is something like that possible in Julia? Is it bad practice? Is there a central/global names space where I can define/extend functions only once, and then those functions are accessible everywhere?

What you describe is typically called an Interface.
You can find examples for interfaces here Interfaces · The Julia Language

It could look like this

module SolverInterface
     abstract type AbstractSolver end;
     solve(solver::AbstractSolver) = nothing
     export AbstractSolver
     export solve
end

then users could use this template to define their own solvers

using SolverInterface

struct MySolver <: AbstractSolver
      solution::Int64
end

function SolverInterface.solve(solver::MySolver)
    return solver.solution
end

solver = MySolver(42)
solve(solver)

In this way it is clear that everyone has to extend the function from SolverInterface, that resolves the problem you pointed out that people would have to overwrite the functions for each other’s modules.

2 Likes

This should be

function SolverInterface.solve(solver::MySolver)

otherwise a new function CurrentModule.solve will be created.

Alternatively, solve could be explicitly imported: import SolverInterface: solve

1 Like

Take a look at something like DataAPI.jl which solves this problem.

Have a third package which defines the core functions, everything else extends them.

1 Like

Thanks, I edited it.