7 ms·
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 abo
by compumike 6y ago
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/ 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.
- kpgiskpg 6y ago(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.
- schoen 6y ago> 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.)*
- kpgiskpg 6y agoOh 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?
- schoen 6y agoOh 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.
- kd5bjo 6y ago> 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.
- Animats 6y agoThese "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.
- samatman 6y agoConflating 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.
- Animats 6y agoWhat 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.
- WalterBright 6y agoI 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.
- meltedcapacitor 6y agoFair enough, but mainstream languages used in broad commercial settings need moron-immunity more than niche languages attracting a select crowd.
- WalterBright 6y agoD 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 :-)
- pjmlp 6y agoWe still need @safe as default though.
- WalterBright 6y agoThe buffer overflow protection is so important it is there whether the code is marked @safe or not.
- pjmlp 6y agoWith @system by default it is harder to track down uses of .ptr
- dkersten 6y agoWhile 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... ;-)
- tsimionescu 6y agoIsn'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).
- dkersten 6y agoI 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.
- tsimionescu 6y agoI'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.
- samatman 6y agoI 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.
- b2gills 6y agoThe 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?
- dragonwriter 6y ago> 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.
- 5y ago
- edflsafoiewq 6y agoWell of course, if you look at "mathy" operators like +. If you look at operator[], the use cases are all "collectiony" objects instead.
- AnimalMuppet 6y agoI could even see + on collections: "Add all the elements of this collection to that collection."
- erik_seaberg 6y agoScala 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.
- joshuamorton 6y agoAnd 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.
- Someone 6y agoI 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/spirit/introduction.html https://www.boost.org/doc/libs/1_67_0/libs/spirit/doc/html/s...)
- peq 6y agoAt 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.