Best way to implement +(x::Float32,y::Float32) in 2 different modules?

Hi,

I want to implement

Base.+(x::Float32,y::Float32)

differently in module A, and module B.

In module C, depending on which module (A or B) I use,I want the proper + function to be called (the one in A or B)?

Is there a way to do this?

You can’t. Since it is the same method, the most recent implementation will overwrite.

Also, you should probably not do this at all.

Perhaps you could explain what you want to achieve? Eg a wrapper type would allow you to define your custom Base.+.

2 Likes

how can i do it with a wrapper type?
THanks

Something like

struct Wrapper{T}
    value::T
end

Base.+(a::Wrapper{Float32}, b::Wrapper{Float32}) =
    Wrapper{Float32}(a.value + b.value) # modify this for custom behavior

It’s actually not impossible, this is what I created the ForceImport package for,

module Foo
    export +
    +() = 7
end

module Bar
    using ForceImport
    @force using Foo
end

julia> Bar.:+()
7

Currently being registered in Metadata.

Additionally, since people might not know how powerful the @force is, you can safely extend + locally too

julia> module Foo
           export +
           +(r...) = Base.:+(r...)
           +(a::Float32,b::Float32) = "Float32 +"
       end
Foo

julia> module Bar
           using ForceImport
           @force using Foo
           println(1.0+sqrt(2))
           println(Float32(1.0)+Float32(sqrt(2)))
       end;
2.414213562373095
Float32 +

As you can see, you can safely extend the Base definition of + and replace the dispatch for Float32, and still retain the dispatch for all the other types on +. The @force is very powerful indeed.

1 Like

Sorry if I missed something about ForceImport, but are you sure this will implement two different methods for the same signature, which is what @Qiyamah asked for?

As I understood it, he wants to define multiple versions of + in various modules. Obviously, he cannot import the + of both of those modules in the same package. However, with ForceImport, you can indeed make different definitions of + in different modules, and then be able to use it in another package.

@Qiyamah will have to clarify what exactly he was trying to do. If he is really trying to import the redefined method from two different packages in a single scope, and they are both the same signature, then it’s not possible without prefixing the call with the module name. What I proposed is for the situation, if he wanted to replaced the Float32 definition of + in some module, and then use it in another module. You could have multiple modules each defining + differently for Float32, but you could only import one of them per module.

You can already do that with qualified names A.+ and B.+ or with an explicit import A.+ statement (if you only need either A or B but not both) without ForceImport, as you well know. ForceImport just does the latter silently.

The real question here is what the original poster is actually trying to accomplish. @Qiyamah, don’t tell us the means you want to employ, tell us the end.

4 Likes

This is what I am trying to do.

module MyModule

	export Ding,foo,DD

	abstract type Ding{T} end;

	struct DD<:Ding{Float64}
		x::Float64
	end

	function foo(x::Ding{Float64})
		(x+x)+x
	end
end

module PlusC1
	using MyModule

	struct C1
		#stuff
	end

	const inst = C1()

	Base.(:+)(x::Ding{Float64},y::Ding{Float64}) = plus(x,y,inst)

	function plus(x::Ding{T},y::Ding{T},c::C1) where T
		println("C1")
		x
	end
end


module PlusC2
	using MyModule

	struct C2
		#stuff
	end

	const inst = C2()

	Base.(:+)(x::Ding{Float64},y::Ding{Float64}) = plus(x,y,inst)

	function plus(x::Ding{T},y::Ding{T},c::C2) where T
		println("C2")
		x
	end
end


module Test1
	using MyModule
	using PlusC1

	foo(DD(21.1)) # should print C1\n C1\n
	
end


module Test2
	using MyModule
	using PlusC2

	foo(DD(21.1)) # should print C2\n C2\n
end

As you see, the + operation in foo must be bound according to the module i am importing which defines + in a new fashion.

This is the problem i am trying to solve,
Thanks

You are still explaining the means and not the end. What is the end goal?

1 Like

If you’re really dead set on using + syntax, you might write macro first_plus that rewrites a + b to first_plus(a, b) in the first module and macro second_plus which rewrites it to second_plus(a, b) in the second module.

Thanks, that i already thought , but i am asking whether there may be a multidispatch way without even involving the macros. that macro is not the best idea, if this module, do this, if that module do that

Another option is to just use a different unicode symbol (which is easy with tab completion), such as ⊞ (available at the REPL via \boxplus-TAB).

Out of curiosity, what language are you primarily coming from? Just trying to establish priors and worldview.

The confusion I have with your example is that you seem to be doing two slightly different things:

  1. You want to have a plus function defined in 2 different modules, operating on 2 different types. This seems perfectly reasonable to me, as there is no ambiguity and they have different signatures. In C++ or something like that, you could just keep the functions in the appropriate module and it wouldn’t be an issue (even if you use import instead of using). However, in Julia there is no ADL so to get this to work you need to combine things in a single namespace. For things like + people put it in Base but for other function names you might just need to fudge around with the modules.
  2. The bigger issue is that you seem to want to have the same signature (i.e. (:+)(x::Ding{Float64},y::Ding{Float64}) ) do different things. This doesn’t work very well in Julia, where you have to put the (:+) methods in Base, but I am suspicious that ADL may not really help your design much here.

C++ guy here!

Basically you are telling me that i can’t choose which :+ method applies within a module, because there is only one namespace.

Then julia provides a limited OO experience. and compensate for it with macros.

This doesn’t work either:

module Arith
	export Rep, DD
	abstract type Rep{T} end
	struct DD{T<:Float64} <: Rep{T}
		x::T
	end

	Base.:+(x::Rep{Float64},y::Rep{Float64}) = plus(x,y)
end

module P1
	using Arith
	export plus
	plus(x::DD, y::DD) = DD(x.x+y.x)
end


module P2
	using Arith
	export plus
	plus(x::DD,y::DD) = DD(x.x-y.x)
end

module X1
	using Arith
	using P1
	DD(4.2) + DD(4.3)
end

module X2
	using Arith
	using P2
	DD(4.2) + DD(4.3)
end

It says
ERROR: UndefVarError: plus not defined
Stacktrace:
[1] +(::Arith.DD{Float64}, ::Arith.DD{Float64}) at ./REPL[1]:8ball:

So how can i selectively bind :+ within a module?

Thanks

plus is not defined in the scope of Arith so you get the UndefVarError.

No, there is not only one namespace. In fact, the reason why you get your error is because there are mutliple namespaces.

Well, julia is not an object-oriented language. But there is nothing to compensate for, multiple dispatch is in my opinion in most cases cleary superior over OO.

4 Likes

I wouldn’t say that, and I don’t think this has anything to do with OO. The issue you are having has to do with your mental model of how to look up the valid methods for a given function name. What I would say is that there is a worldview clash between people coming from C++/Java/Python/C#/etc. and Lisp - where many of the core developers using Julia come from a Lisp mindset.

If you come from an OO (i.e. single-dispatch) world it is immediately obvious that the namespace of the type provides valid methods to operate on that type (hence, you can always keep operator+ in the namespace of the type it applies to in any OO language), and if you come from function overloading (i.e. C++) then there is ADL which means the compiler looks in the namespace of any of the types in the signature of the function.

If you read through the later parts of Function name conflict: ADL / function merging? you will finally see that we figured out why namespaces in Julia are so confusing to people from C++/Java/Python/C# and make complete sense to people who used CLOS. Hopefully there will be a way to reconcile the worlds.

In principle you could put a :+ wherever you want. In practice you are going to have to put it in the Base module. As for other functions, if you want to easily use the same function name on completely different type signatures, then you generally have to find a shared namespace to put them in. Finally, if you want to use any of the function names that are used in Base for your own types, then you probably want to just put those functions in Base to make your life easier. It may be ugly, but it is a reasonable medium-term solution… (and longterm solution if the Lisp-style thinking on function lookup is maintained).

But, in your exact example, I am not sure if a C+±style ADL approach would work either (at least for the operator+ part. The plus part would be in C++ fine since the signatures operate on distinct types.

This has nothing to do with OO, nor does it exclude multiple-dispatch. It is just that people who come from an OO language expect there to be some sort of argument dependent lookup of valid methods by looking at the namespace of the type it is called on (even if they don’t realize that is how single-dispatch languages work).

Keep in mind that in C++, and other languages with function overloading, it is kind of like multiple-dispatch with the type is known at compile-time. Eventually, C++ may also even have something that does that dynamically (http://www.stroustrup.com/multimethods.pdf), just like in julia. The name lookup rules are orthogonal to all of this (even if the implementation can get tricky in a more dynamic language).

2 Likes

That’s because you never defined what plus is in the Arith module. You can do it like this:

module Arith
	export Rep, DD
	abstract type Rep{T} end
	struct DD{T<:Float64} <: Rep{T}
		x::T
	end
end

module P1
	using Arith
	export +
        +(r...) = Base.:+(r...)
	+(x::DD, y::DD) = DD(x.x+y.x)
end


module P2
	using Arith
	export +
        +(r...) = Base.:+(r...)
	+(x::DD, y::DD) = DD(x.x-y.x)
end

module X1
	using Arith, ForceImport
	@force using P1
	DD(4.2) + DD(4.3)
end

module X2
	using Arith, ForceImport
	@force using P2
	DD(4.2) + DD(4.3)
end

That shouldn’t give you an error, the problem is you had a plus function that wasn’t defined in Arith.

1 Like

You still haven’t explained what you’re trying to accomplish. All you’ve done is posted meaningless toy snippets. (I don’t want you to post production code, I want you to describe what your code is really trying to do.)

I have a feeling that you’re trying to shoehorn some completely foreign abstraction into Julia that would be implemented in an entirely different way by an experienced Julia programmer.

7 Likes