How about direct sum ⊕ as vector attach operator?

Background

I know that Julia’s design is very reasonable, especially from the viewpoint of mathematics; it’s my favorite feature of Julia. For example, Julia uses * as a string join operator instead of +, unlike many programming languages, because it makes sense in the context of (abstract) algebra:

\text{"hello"} \ast \text{"world"} = \text{"helloworld"}

is much more natural than

\text{"hello"} + \text{"world"} = \text{"helloworld"}

obviously. But sometimes I miss some operators in Python. In this context, + serves as a vector concatenator.

Python 3.11.4 (tags/v3.11.4:d2340ef, Jun  7 2023, 05:45:37) [MSC v.1934 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> x = [1,2]
>>> y = [3,4]
>>> x + y
[1, 2, 3, 4]

Of course, I think vcat(x, y) or [x; y] in Julia are also great.

julia> x = [1,2]
2-element Vector{Int64}:
 1
 2

julia> y = [3,4]
2-element Vector{Int64}:
 3
 4

julia> vcat(x, y)
4-element Vector{Int64}:
 1
 2
 3
 4

julia> [x; y]
4-element Vector{Int64}:
 1
 2
 3
 4

I understand that this grammatical consistency is important for reading or writing code. Most significantly, I know that x + y is totally weird in a mathematical sense.

Then how about \oplus? Usually, in general mathematics, a direct sum \oplus (\oplus) refers to a Cartesian product of vector space or some algebraic structures. For instance, the plane \mathbb{R}^{2} and the z-axis \mathbb{R}^{1} could be merged in 3-dimensional space \mathbb{R}^{3} by \mathbb{R}^{3} = \mathbb{R}^{2} \oplus \mathbb{R}^{1}. In a coordinate system, it’s exactly the same as vector concatenation, (x, y, z) = (x, y) \oplus (z).

Implementation

Below are simple implementations. If you love these, you can use them personally even if Julia doesn’t change. I believe all of you guys have a personal library, just for yourselves. :joy:

Binary operation

Please note that I didn’t check for performance issues. vcat or other ways could be more efficient and fast.

julia> ⊕(x, y) = [x; y]
⊕ (generic function with 1 method)

julia> [1, 2] ⊕ [3, 4]
4-element Vector{Int64}:
 1
 2
 3
 4

\bigoplus_{k} A_{k}

Since \oplus is a binary operation, we can generalize it using reduce.

julia> reduce(⊕, [[1, 2], [3, 4], [5, 6, 7]])
7-element Vector{Int64}:
 1
 2
 3
 4
 5
 6
 7

The following is a method form.

julia> ⊕(x, y...) = reduce(⊕, [x, y...])
⊕ (generic function with 2 methods)

julia> ⊕([1, 2], [3, 4], [5, 6, 7])
7-element Vector{Int64}:
 1
 2
 3
 4
 5
 6
 7

Clean code

⊕(x, y) = [x; y]
⊕(x, y...) = reduce(⊕, [x, y...])

Any comments or ideas are helpful to me. Thank you for your attention!

You can of course introduce this definition for the operator in your own code or package, but adding it to Julia Base is likely not a good idea, at least not as an exported symbol. Doing so would break most code that already defines this operator for themselves. Here’s a list of registered packages that already make use of this operator:
https://juliahub.com/ui/Search?q=⊕&type=symbols

Base is allowed to export new names; it won’t break any code that defines or imports this operator, only code where there is ambiguity.

seems fine if that’s what it means — are there other mathematical usages of that would conflict with this meaning?


Looking through the other languages, I see infix operators:

,
++
∾
+
~
&
@
|||
<<

of which Julia has available

++
∾
~
@
|||

It would be nice to even have a function to concatenate sequences. vcat maybe should’ve been that but it does weird stuff a lot of the time.

julia> vcat("a", "b")
2-element Vector{String}:
 "a"
 "b"

I’d be happy enough with an append or concat.

Cf. https://github.com/JuliaLang/julia/issues/11030 and RFC: deprecate * in favor of new ++ concatenation operator by stevengj · Pull Request #22461 · JuliaLang/julia · GitHub for some related old discussions.

Even if “base is allowed to export new names”, it might not be a good idea and in the best interest of the users of the language.

And it does break importing code

julia> module Foo
       export sin
       sin(x) = 0
       end
Main.Foo

julia> using .Foo

julia> sin(2)
WARNING: both Foo and Base export "sin"; uses of it in module Main must be qualified
ERROR: UndefVarError: `sin` not defined
Stacktrace:
 [1] top-level scope
   @ REPL[3]:1

That’s the ambiguous case. The unambiguous case is

julia> module Foo
              export sin
              sin(x) = 0
              end
Main.Foo

julia> using .Foo: sin

julia> sin(2)
0