News for hackers. Stuff that matters.
Re:Not Growing a Language by compumike on Saturday April 03, 2021 @01:41PM (#26682991)
The article suggests a use case for operator overloading in a Complex or BigInteger class.

My programming experience is that that these "mathy" objects are about the only unambiguous use case for operator overloading. Given the limited use case, I'm not sure whether it's worth the added complexity for language designers.

I wrote the simulator engine code for CircuitLab (https://www.circuitlab.com/). We have complex numbers, sparse matrices, and extended-precision (more than a 64-bit double) floating point classes, all of which we use extensively within the simulation engine. Yes, it can be a bit annoying and verbose to do X.add(Y) instead of X+Y, but I tend to just leave an end-of-line comment to aid readability later. And this mathematical core code tends to be infrequently changed and well covered by tests.

Re:Not Growing a Language by kpgiskpg on Saturday April 03, 2021 @02:15PM (#26683303)
(Author here).

Here's a not-so-mathy application that I had recently. I was implementing a units system in Python. Operator overloading allowed me to define joules like so: `J = KG * M**-1 * S**-2`. Then I could define grays (a radiation-related unit) like `Gy = J/KG`. Repeat for hundreds of units. If I had to do it in Java, I'd be frustrated by the verbosity and it would be easier to make mistakes.

My point -- Guy's point, actually -- is that if you don't give people the ability to grow a language, then their expressive power will be limited. And you can't anticipate ahead of time all the ways they'll want to express themselves.

Admittedly, my application is kinda mathy under the hood, because the units exist in a vector space. I guess that's to be expected when the operators come from maths.

Re:Not Growing a Language by schoen on Saturday April 03, 2021 @03:19PM (#26683887)
> Operator overloading allowed me to define joules like so: `J = KG * M*-1 * S*-2`. Then I could define grays (a radiation-related unit) like `Gy = J/KG`. Repeat for hundreds of units.

Presumably exponentiation rather than multiplication by a constant, right?

This is cool and reminds me of how the Python Z3 binding uses operator overloading to let you express arithmetic and logical constraints

  >>> import z3
  >>> s = z3.Solver()
  >>> a, b, c, d = (z3.Int(x) for x in "abcd")
  >>> s.add(a*b == c+d)
  >>> s.add(a+b == c+d)
  >>> s.add(a!=0,b!=0,c!=0,d!=0)
  >>> s.check()
  sat
  >>> s.model()
  [d = 6, c = -2, b = 2, a = 2]
  >>> s.reset()
  >>> s.add(a>1, b>1, c>1)
  >>> s.add(a*a + b*b == c*c)
  >>> s.check()
  sat
  >>> s.model()
  [c = 17, b = 8, a = 15]
(It's limited in some ways for some reasons, like for some types and relations you may have to use a Z3 function to express a relation even though Python has syntax for it -- the example that I know of is that you have to use z3.Or(), z3.And(), etc., with boolean variables instead of Python's builtin boolean operators. Not sure why.)*
Re:Not Growing a Language by kpgiskpg on Saturday April 03, 2021 @03:25PM (#26683941)
Oh wait, Hackernews seems to have removed one of the astericks symbols. It should be Python's exponentiation operator (2 astericks symbols), ya. (edit: fixed now).

That's cool too! How do you define the variables?

Re:Not Growing a Language by schoen on Saturday April 03, 2021 @03:49PM (#26684132)
Oh wait, I left that part out! I'll edit my post.

... there we go.

You use Z3 objects that represent unknowns of particular types, like Z3.Int, Z3.Bool, etc. Each one returns an object representing a variable of that type, which can then be used in (Python-formatted!) expressions that you give to a solver.

Re:Not Growing a Language by WalterBright on Saturday April 03, 2021 @03:51PM (#26684150)
I agree that operator overloading should be restricted to math objects. D tries to make it unpleasant to do it for non-math objects. For example, < <= > >= are not overloadable separately, just one function `opCmp` does all of them. Non-math operators are not overloadable.

The D community has generally agreed, and we haven't had much trouble with people overloading operators to do a regular expression DSL, for example.

Re:Not Growing a Language by meltedcapacitor on Saturday April 03, 2021 @07:36PM (#26685698)
Fair enough, but mainstream languages used in broad commercial settings need moron-immunity more than niche languages attracting a select crowd.
Re:Not Growing a Language by WalterBright on Saturday April 03, 2021 @09:40PM (#26686266)
D does not have a huge market presence, but it is not a niche language. It is very capable for a diverse variety of projects. Whenever you get tired of buffer overflows and preprocessor abuse, come check us out :-)
Re:Not Growing a Language by pjmlp on Sunday April 04, 2021 @04:12AM (#26687743)
We still need @safe as default though.
Re:Not Growing a Language by WalterBright on Sunday April 04, 2021 @04:51PM (#26692723)
The buffer overflow protection is so important it is there whether the code is marked @safe or not.
Re:Not Growing a Language by pjmlp on Sunday April 04, 2021 @05:19PM (#26692945)
With @system by default it is harder to track down uses of .ptr
Re:Not Growing a Language by Animats on Saturday April 03, 2021 @03:08PM (#26683781)
These "mathy" objects are about the only unambiguous use case for operator overloading.

Yes. Most other uses for operator overloading are really chains of functions. There's better syntax for that.

I once wrote some overloads for C++ so that you could write

    result = fileobject | item | item | item | item;
and then apply either "read" or "write" to "result". This allowed marshalling without writing the item list more than once. Bad idea.

Python is notorious for problems that stem from combining operator overloading and implicit conversions. "+" is arithmetic for some types and concatenation for others.

    [1,2,3] + [4,5,6] 
is

[1,2,3,4,5,6]

which you might not expect. If you add arrays from numpy, those add arithmetically. You can add a numpy array to a built-in array. Trouble comes when you pass a numpy array to a function that expects a built-in array, and the wrong semantics of "+" are applied.

Rust has operator overloading without implicit conversions. That avoids such ambiguities, at the cost of requiring many explicit conversions.

Re:Not Growing a Language by samatman on Saturday April 03, 2021 @04:50PM (#26684612)
Conflating concatenation and addition is just a terrible mistake, this is one illustration among many of that.

In Lua, `+` is always addition, and can be overloaded with an `__add` metamethod, while `..` is concatenation, overloaded with a `__concat` metamethod. `..` is also right associative, which means that `a .. b .. c .. d`, a normal enough string-building pattern, can be optimized into a single allocation for the new string, without having to create `a ..b` and then `ab .. c` and so on.

So I don't see this as a problem of operator overloading, I see it as a problem of a missing operator. Implicit conversion makes that problem worse, but `"12" .. 13` in Lua will give you "1213", as you would expect, and `"2" + 3` gives you 5, again, as you would expect.

Re:Not Growing a Language by fiddlerwoaroof on Saturday April 03, 2021 @05:54PM (#26685084)
Concatenation is just addition, though: addition is the monoid operation on numbers with the identity of 0, concatenation is the monoid operation on lists with identity of [].

The problem is almost always not the overloading of + but the implicit type coercion rules

Re:Not Growing a Language by gowld on Saturday April 03, 2021 @10:02PM (#26686347)
List[Int] has more than one monoid
Re:Not Growing a Language by fiddlerwoaroof on Saturday April 03, 2021 @11:06PM (#26686584)
But, when I'm thinking in List[Any], I expect to be working on the Monoid for List[Any], not the Monoid for List[Int]: things like special-casing List[Int] are exactly what make programming languages difficult to learn and use.
Re:Not Growing a Language by Animats on Saturday April 03, 2021 @05:10PM (#26684777)
What to use for concatenation is a problem. PL/1 used "|", which was originally drawn with a break in the middle. But C took that over as "or", and that's now the accepted standard. ".." is accepted as a range operator now.

Moving deeper into Unicode is probably not the answer. Although, if a language needed a concatenate symbol, ⊞ (Squared Plus) might be a good choice. It's not used for much else.

Re:Not Growing a Language by nicoburns on Saturday April 03, 2021 @06:16PM (#26685235)
Perhaps ++, and just use += 1 for incrementing. Rust already doesn't have an increment operator, and my JavaScript linter forbids it.
Re:Not Growing a Language by Someone on Saturday April 03, 2021 @07:00PM (#26685501)
If it didn’t cause problems (ones I can think of in seconds: it complicates parsing, mathematicians will think it signals multiplication, and it makes it hard to insert spaces), using a single space for concatenation would make sense to me.

(Aside: C (or the C preprocessor?) is the only piece of software that I know that supports that , but only for string literals)

Re:Not Growing a Language by lifthrasiir on Saturday April 03, 2021 @06:01PM (#26685129)
As a binary operator `~` seems pretty unambiguous.
Re:Not Growing a Language by samatman on Saturday April 03, 2021 @10:13PM (#26686389)
Yes, my vote for languages which are running out of glyphs would be `~~` actually.

Rather than using context to differentiate bitwise NOT from concatenation, just rule that ~~ is always concatenation. What you lose is just "NOT NOT", which (without this operator) is legal but pointless.

Re:Not Growing a Language by guram11 on Saturday April 03, 2021 @11:51PM (#26686754)
and to make things weirder python can concat literal strings together with "space operator"(j/k)

    >>>'a' 'b' 'c'
    >>>'abc'
Re:Not Growing a Language by marvy on Sunday April 04, 2021 @02:24PM (#26691682)
they got rid of that in Python 3, I think
Re:Not Growing a Language by thaumasiotes on Saturday April 03, 2021 @07:37PM (#26685707)
> Python is notorious for problems that stem from combining operator overloading and implicit conversions. "+" is arithmetic for some types and concatenation for others. `[1,2,3] + [4,5,6]` is `[1,2,3,4,5,6]`, which you might not expect.

It seemed obvious to me as soon as I saw it in Perl that the Perl approach ("+" for adding numbers, "." for concatenating strings) is a much better idea than using "+" for both operations.

I'm not sure why people keep wanting to use + for concatenation.

(My other language pet peeve is using backslash as the escape character for your mini-DSL that gets parsed from strings, when backslash is also the escape character for strings. You want to use separate escape characters for each context; stacking the same one in several contexts is the worst possible approach.

C was aware of the problems: it uses `\` as the escape for strings and `%` as the escape for printf commands. But somehow by the time we were writing regular expression libraries, this simple, obvious technology had been lost.)

Re:Not Growing a Language by samatman on Saturday April 03, 2021 @04:44PM (#26684562)
I know of at least one other use of operator overloading which is quite pleasant to use. In Lua's PEG engine, Lpeg, operator overloading is used to express combinators, so `P"a" * P"b"` matches "ab" and `P"a" + P"b"` tries to match "a", then tries to match "b".

It's a little bit of a kludge! But a clever one, which uses the precedence of the existing operators to match the expected precedence of concatenation and the `/` ordered choice operator from PEG grammars.

In general I'm suspicious of arguments that boil down to "people might use this language feature badly". I'm not sure I want the full ability to add my own infix and postcircumfix operators, the way e.g. Raku allows, but there's no denying that being able to write `if foo ∈ test_set` is expressive and cool.

Maybe expressive and cool isn't a terminal value for language design, maybe `if test_set.element(foo)` is good enough. I kinda like it though.

Re:Not Growing a Language by b2gills on Tuesday April 06, 2021 @10:44PM (#26720028)
The Raku idea is that an operator only does one thing. That way you can tell at a glance what the operator is doing. If you want to do something else, create a new operator. (One of the design mottos was “Similar things should look similar, and different things should look different”.)

In languages which only let you overload existing operators; those operators often get used for a bunch of different things.

I mean, how many languages use `+` for both addition and string concatenation? … and maybe also set union?

Re:Not Growing a Language by dragonwriter on Wednesday April 07, 2021 @06:49AM (#26723013)
> The Raku idea is that an operator only does one thing.

Its kind of interesting that Raku’s own docs think of '=' being used for both item assignment and lost assignment as an exception to this, because it illustrates how narrowly Raku defines “one thing”. (As does the use of different postcircumfix operators for positional and associative access, which most languages call indexing, and use one construct for, which may not even technically be an operator.)

But its not just freedom to create new operators that allows this; for it to work Raku has a huge number of built-in basic operators, plus the hyper- and meta-operators.

Re:Not Growing a Language by b2gills on Sunday April 18, 2021 @04:31PM (#26856037)
Actually `=` sort-of only does one thing. It asks the left argument what it wants to do with the values.

    my @a = 1,2,3;
is almost the same as

    my @a;
    @a.VAR.STORE((1,2,3));
---

As for separate positional and associative access:

Raku has separate positional and named arguments to functions.

An argument list is actually a singular thing called a Capture. Even if it usually pretends to not be.

    my \capture = \( 'a', b => 10 ); # Capture litteral

    say capture[0];   # a
    say capture{'b'}; # 10


    sub foo ( |capture ) {
      say capture[0];   # a
      say capture{'b'}; # 10
    }

    foo |capture; # give it the above capture litteral
    # foo 'a', b => 10

    sub bar ( $a, :$b ){
      say $a; # a  (positional)
      say $b; # 10 (named)
    }

    bar |capture; # give it the above capture litteral
    # bar 'a', b => 10
To be able to easily extract out those parts, it helps if there is a different way to get at each.

The same thing also applies to extracting information out of regexes

    'a10' ~~ / (.) # $0
       $<b> = (..) # $<b>
    /;

    say $0;   # a
    say $<b>; # 10

    say $/[0];   # a  (positional index into $/)
    say $/{'b'}; # 10 (named index into $/)
Re:Not Growing a Language by dkersten on Saturday April 03, 2021 @04:39PM (#26684520)
While in most of my code, I don't really care about operator overloading (ie not having it would not make my life any worse), but when I write shader code (or a C++ library like GLM) to do mathy stuff, but including vectors and matrices, I am very glad to have overloaded operators and dread the idea of having to work around the language to express what I want. I'm not thrilled about having to duplicate the code (once using X.add(Y) and a comment to show a clearer version) here, but its better than nothing.

Of course, if we just use s-expressions, then there's no difference between operators and functions and this whole thing becomes moot. Although, with the caveat that your nice infix math expressions are still not nice infix math expressions. Then again, if we are using sexps, then we're likely using a language with macros and writing an infix macro for those mathy parts wouldn't be such a bad idea... ;-)

Re:Not Growing a Language by tsimionescu on Saturday April 03, 2021 @06:21PM (#26685266)
Isn't operator overloading usually a bad idea for high-performance maths? It limits you to binary operations (unless you go down an even deeper rabbit hole of template meta-programming), when many oprimized algorithms are ternary or even higher arity (e.g. optimized matrix multiply-and-add).
Re:Not Growing a Language by dkersten on Monday April 05, 2021 @08:10AM (#26697956)
I mean, I haven't had to write one myself, but as a user of GLM, I've been very happy and personally couldn't care less if they're template meta-programming heavy to achieve it. As a user of the library, it doesn't bother me. I use plenty of template-heavy libraries anyway.

Maybe its hard to achieve absolute best performance, but I'm not arguing for that, I'm only arguing that operator overloading leads to better programmer ergonomics. I can benchmark and convert to optimised functions after I have it working nicely. Ergonomics first, optimisations after.

Re:Not Growing a Language by tsimionescu on Tuesday April 06, 2021 @03:30AM (#26709078)
I'm not familiar with GLM, but it seems to be the perfet sweetspot for operator overloading - it works with fixed size, small matrices (up to 3x3), which probably don't require and may not even benefit from more advanced algorithms.

The problems I had read about applied to operations on larger matrices, where the difference between n^3 and n^2.5 or whatever becomes extremely noticeable, and makes it worth to write mulAndAdd(a, b, c) rather than a*b+c.

Re:Not Growing a Language by Someone on Saturday April 03, 2021 @06:49PM (#26685436)
I think many somewhat mathy DSLs can benefit from it.

For example, there’s boost Spirit, a library to write parsers. You describe a grammar in something that looks a lot like a EBNF grammar (https://www.boost.org/doc/libs/1_67_0/libs/spirit/doc/html/s...)

Re:Not Growing a Language by kd5bjo on Saturday April 03, 2021 @02:46PM (#26683591)
> My programming experience is that that these "mathy" objects are about the only unambiguous use case for operator overloading.

Rust's ability for smart pointers to overload the dereference operator works pretty well, though there is still some potential for abuse.

Part of what makes it reasonable is the required function signature: It can only ever produce a reference to some other type, which rules out some of the crazier possibilities.

Re:Not Growing a Language by edflsafoiewq on Saturday April 03, 2021 @05:10PM (#26684773)
Well of course, if you look at "mathy" operators like +. If you look at operator[], the use cases are all "collectiony" objects instead.
Re:Not Growing a Language by joshuamorton on Saturday April 03, 2021 @07:27PM (#26685650)
And note that these overlap: think matrices where a + b adds, and a[1:,:,0] may have meaning, requiring the index operator to support tuples and range objects.
Re:Not Growing a Language by AnimalMuppet on Saturday April 03, 2021 @06:38PM (#26685383)
I could even see + on collections: "Add all the elements of this collection to that collection."
Re:Not Growing a Language by erik_seaberg on Saturday April 03, 2021 @07:11PM (#26685571)
Scala used + for an element and ++ for another collection

  scala> 1 +: List(2, 3) ++: List(4, 5) :+ 6
  res0: List[Int] = List(1, 2, 3, 4, 5, 6)
where +: is right-associative and implemented by the list rather than the element.
Re:Not Growing a Language by peq on Saturday April 03, 2021 @07:15PM (#26685589)
At first "mathy" objects seem not so important, but it also involves:

- Vectors, matrices

- Immutable containers (lists, sets, maps, etc.)

- Domain specific languages (regular expressions, constraint languages, etc.)

And without good support for matrices, the data science and ML people will just use Python instead of Java to build their libraries. And then everyone will use the language with the best libraries, even if they don't use matrix math in their code.

For regular expressions, you have to use a DSL embedded in Strings in Java instead of using operators like | as operator on regular expressions directly.