Introduce ++ as the concatenation operator

It seems like it would be handy to have ++ as a generic concatenation operator. There are a few languages that use ++ for this, e.g. Erlang, Haskell, and Elm. For arrays, ++ would be sugar for vcat (hcat is another story). Here’s an example of the current syntax pulled from a unit test in MLJBase.jl:

N = 2
v = vcat(fill(:a, N), fill(:b, 2N), fill(:c, 3N), fill(:d, 4N))

Of course, Julia already has syntax sugar for vcat, but in this case the sugar is not very sweet:

v = [fill(:a, N); fill(:b, 2N); fill(:c, 3N); fill(:d, 4N)]

Compare this to the proposed ++ operator:

v = fill(:a, N) ++ fill(:b, 2N) ++ fill(:c, 3N) ++ fill(:d, 4N)

I find the ++ operator to be more readable.

And of course ++ could be used for string concatenation:

s = "hello " ++ "world"

I don’t think I’m alone in finding * to be unintuitive for string concatenation. And it seems to me like concatenation is mostly a separate concept from arithmetic, so it would be better not to overload arithmetic operators to perform concatenation.

9 Likes

Btw you can already do this:

julia> (++)(x::AbstractVector, y::AbstractVector) = vcat(x, y)
julia> rand(5) ++ rand(7)
julia> (++)(x::AbstractString,y::AbstractString) = x*y
julia> "hello" ++ "goodbye"

whether that’s recommended is another story, but I guess you could just use that in your pkg.

With a bit of polishing for dealing with missings, nothing etc it could even make for a simple little package PlusPlus.jl :joy:

PS: hold my beer… incoming

13 Likes

Yeah, I agree it can be done, but I’d rather not introduce non-standard syntax into my code for a basic operation like concatenation. :slight_smile:

Are you referring to PlusPlus.jl?

3 Likes

aaah indeed, ok well I guess I can shelve https://github.com/tlienart/PlusPlus.jl :beer:

2 Likes

“But making Julia packages is hard!”

1 Like

Well, it looks like GitHub - pkofod/PlusPlus.jl is not exactly maintained, and it’s not in the general registry, so I think the path is clear for you!

It’s probably appropriate for x ++ nothing and x ++ missing to throw method errors, so I’m not sure if you need special handling for missing and nothing

1 Like

Haha I put it together more for fun than anything but I guess if someone finds that useful I’m happy to think about it a bit harder and register it :man_shrugging:, not really sure it’s worth it at the moment

3 Likes

What would that mean in this context? Is there anything to maintain?

4 Likes

Ha, yeah there’s not much to maintain. But the string concatenation method probably needs to be updated from this:

++(a::AbstractString, b::AbstractString) = a*b

to this:

++(s1::Union{AbstractChar, AbstractString}, ss::Union{AbstractChar, AbstractString}...) = *(s1, ss...)

to align with the definition in base Julia, and because ++ is currently parsed as an n-ary operator:

julia> ex = :(x ++ y ++ z)
:(x ++ y ++ z)

julia> dump(ex)
Expr
  head: Symbol call
  args: Array{Any}((4,))
    1: Symbol ++
    2: Symbol x
    3: Symbol y
    4: Symbol z

EDIT:
In this context I guess by “maintained” I meant updated to use a Project.toml file and added to the general registry.

1 Like

Why not? I say go for it if you like that syntax :slight_smile:

One of the beautiful things about Julia is that you can do things like this with no loss in performance.

2 Likes

I think one problem with using ++ for concatenation is that it is not immediately clear what operator you can use for what ^(::String, ::Integer) does. In this sense, I think even + is a bit better than ++ as you can guess *(::Integer, ::String) might work.

Of course, we can’t use * to concatenate vectors. So, I often wonder if it makes sense to be able to define “local algebra”; e.g.,

@withalgebra * = vcat begin
    [:a]^N * [:b]^$(2N) * [:c]^$(3N) * [:d]^$(4N)
end

is lowered to something equivalent to

let *′ = *, * = vcat, ^ = repeat
    [:a]^N * [:b]^(2 *′ N) * [:c]^(3 *′ N) * [:d]^(4 *′ N)
end

and

@withalgebra * = ∘ begin
    first * tail^3
end

is lowered to

let * = ∘, ^ = power_by_squaring_with(*)  # to be defined
    first * tail^3
end

I find it interesting. It is one of the three operators special-cased by the parser. Other operators like are parsed as binary operator. So, in this sense, I guess using ++ = vcat kind of makes sense (i.e., vcat(vcat(a, b), c) requires a temporary array but vcat(a, b, c) doesn’t).

4 Likes

I think that a package is the best place for this. ++ is one of the three operators that chains its arguments (the other two are + and *) so it is best to leave it undefined in Base if users need it for something custom.

Concatenation is a nice use case, but it is just one of the possible nice use cases.

5 Likes

Yeah, that thought crossed my mind, but we would still have repeat("ab", 3). That’s more verbose than "ab" ^ 3, but I think string concatenation is much more common than string repetition.

The local algebra macro is a neat idea.

For extensive previous discussion on this topic, and why we opted not to do this yet in Base, see
#11030 and #22461. My personal conclusion after actually trying to implement this in Base can be read here.

12 Likes

I’d be happy with just a generic concat(args...), if only so that I don’t have to remember how to concatenate tuples. Most code is not performance sensitive.

1 Like

Thanks for the links!

1 Like

it’s a terrific example for those of us who are not exactly sure how the whole Julia package thing works.

1 Like

I realise this has been discussed before but I think most new people who come to Julia are really surprised (and probably put off) when they discover that * is string concatenation. It would be a shame if Julia is now stuck with this highly idiosyncratic syntax. Anyone who has ever used another programming language will expect string concatenation to be + or ++. If we want to become more mainstream, this is the sort of oddity that should be removed IMHO. We can make it more Julia-like by writing ++ as ⧺ (as in Unicode Character 'DOUBLE PLUS' (U+29FA) ).

Did you have a look at the link that Steven posted earlier? it’s a good summary: RFC: deprecate * in favor of new ++ concatenation operator by stevengj · Pull Request #22461 · JuliaLang/julia · GitHub

Yes I have, thank you. I think there are two views. One from the inside (those who are julia developers) and one from the outside (users and in particular new users of the language). I was trying to give the view from the outside. Everyone I have ever spoken to who had not yet written a line of Julia (but has written code in other languages) thinks it bizarre/hilarious that string concatenation is *. A slightly awkward compromise might be if the plusplus package was registered and easily installable. New users could all just be told to include that package at the top of their code. It seems a shame though.