boostfs::path path("/some/path");
path /= "yourfile.txt";
Meanwhile, reading your code without being familiar with boostfs::path, my brain grinds to a halt while I try to understand what in the hell dividing by a string is supposed to do.This is one of those "well intentioned" features that turns into a quagmire in practice.
Except when not having it is the worst. Working with BigDecimal in Java is hell because it does not have operative overloading, meaning while you avoid
boostfs::path path("/some/path");
path /= "yourfile.txt";
you get saddled with bullshit like x.add(x.add(ONE).pow(2).subtract(x)) x.add(x.add(ONE).pow(2).subtract(x))
Especially because sometimes it is difficult to decide whether you should have x.foo(y) or y.foo(x). However, add(x, sub(pow(add(x, ONE), 2), x))
Is perfectly acceptable in my opinion (add some indentation if it's difficult to read). As long as you can pass parameters and return values without tricks (ie. passing pointers), this is quite nice. You should even be able to do this in Java with "import static". Or C++ with function overloading.I'm definitely not a fan of C++ -style operator overloading, where you only have a finite amount of operators with set precedence/associativity and then they get overloaded to meanings that the symbols don't convey. On the other hand, I'm not sure what to think of Haskell either (where you can define arbitrary operators, like >>=, <+> or <$>). It's not as limited as C++, but there are some quite nasty examples that have gone overboard with operators.
Overall, it seems like operator overloading adds a lot of complexity to a language but the benefit is arguable.
I comletelly agree with this, it is very confusing API.
You should even be able to do this in Java with "import static"
Yep, you should define a static methods add, sub, pow etc with BigDecimal parameters and then use import static.
Something along
class BigDecimalMath { public static BigDecimal add(BigDecimal x1, BigDecimal x2) { return x1.add(x2); } }
Also JVM JIT would very likely inline such code, so you should not really worry about an added method call.
BiFunction<BigDecimal, BigDecimal, BigDecimal> add = BigDecimal::add; add(x, y);
Unfortunately you have to do:
add.apply(x, y);
Which is kind of cumbersome.
Unfortunately they brought something completely different in the name of Default Methods, and that was in my opinion not needed.
func SameSideOfTriangle<N>(p0 Point2D<N>, p1 Point2D<N>, a Point2D<N>, b Point2D<N>) bool where N: Number {
ba := Point2D(b.x.Sub(a.x), b.y.Sub(a.y))
p0a := Point2D(p0.x.Sub(a.x), p0.y.Sub(a.y))
p1a := Point2D(p1.x.Sub(a.x), p1.y.Sub(a.y))
cp0 := ba.x.Mul(p0a.y).Sub(p0a.x.Mul(ba.y))
cp1 := ba.x.Mul(p1a.y).Sub(p1a.x.Mul(ba.y))
dot := cp0.x.Mul(cp1.x).Add(cp0.y.Mul(cp1.y))
return dot >= 0
}
Versus: func SameSideOfTriangle<N>(p0 Point2D<N>, p1 Point2D<N>, a Point2D<N>, b Point2D<N>) bool where N: Number {
ba := Point2D(b.x - a.x, b.y - a.y)
p0a := Point2D(p0.x - a.x, p0.y - a.y)
p1a := Point2D(p1.x - a.x, p1.y - a.y)
cp0 := ba.x * p0a.y - p0a.x * ba.y
cp1 := ba.x * p1a.y - p1a.x * ba.y
dot := cp0.x * cp1.x + cp0.y * cp1.y
return dot >= 0
}Genuinely asking: Why does this function need to be generic over Numbers? Couldn't you implement it once or twice for whatever actually-numeric types you need? How many different Point2D template instantiations do you actually have?
And that's only a small function (only one part of what's needed to check point-triangle intersection!) Copying and pasting e.g. Sutherland-Hodgman clipping or 4D/5D matrix math would get unsustainable quickly.
Granted, you then have the problem of having to deal with overflow on different types, but that's just one of the ocean liner of problems you've signed up for if you're working in the graphics space. May the odds be ever in your favor. ;)
x + ((x + ONE).pow(2) - x)
or x + (pow(x + ONE) - x)On the contrary, when all you have is a couple of examples that aren't even that bad and a slippery slope argument, you are on very thin ground, rhetorically speaking.
path += '/' + 'yourfile.txt';
It's also entirely possible that I'd already subconsciously read ahead and so knew that it couldn't have possibly been division.Nope, this is just incorrect. The C++ is completely dominant in large swaths of the software industry largely because operator overloading allows the writing of generic algorithms (which allows you to write large scale software without losing C-like performance). Without operator overloading, it is much harder to write a function that can be specialized on types that weren't specifically designed for such use.
In Python, Numpy, the Decimal class, I could really give examples for days of cases where operator overloading is essential. Go doesn't have it, Go doesn't have a lot of things, and Go will always be an also-ran language that isn't adopted outside of a very narrow domain.
The fact that built in types are 'special' and only they can support operators such as [] is enough for me to avoid the language. The complete lack of generic programming is more than enough.
Compare (fake Go-with-Swift-generics syntax):
func Distance<N>(x N, y N) N where N Number {
return x.Mul(x).Add(y.Mul(y)).Sqrt()
}
Versus: func Distance<N>(x N, y N) N where N Number {
return Sqrt(x * x + y * y)
}
If your reaction is "well, Distance doesn't look too bad like that", I can replace it with matrix multiplication or point-inside-triangle testing. Not having overloading quickly fails to scale.edit: rereading the earlier comments, this may be orthogonal to the GP's claim that operator overloading is crucial for generics. Ooops. :)
(It's totally fine for a language to be not interested in that domain. But that doesn't mean operator overloading is bad. Overloaded operators are essential for some domains.)
Many times you don't care for the (costly) square root, so a distance-squared function can be useful.
Multiplying x by itself ("squaring") can also be a useful function that is used a lot.
(defun distance (x y) (sqrt (distance-squared x y)))
(defun distance-squared (x y) (+ (square x) (square y)))
(defun square (x) (* x x))
In the same way that we can decompose our code, we can also decompose
the concept of "operator overloading": it gives you is the ability to
use one-letter (1), fixed-arity and precedence-following (P), infix
(I), operators for your own or someone else's operations (G).In languages that support 1PI properties, you'd often overlook such decompositions because it's quick and easy to write sqrt(x * x + y * y). To read it, also, but then you find yourself doing more and more complex calculations in-line. Reading suffers. You may end up with something that's worse than the corresponding code in a language that encourages defining small functions instead. (Lisp, of course, lets you use any combination of these properties, but the latter style is the one normally used.)
Yes, this is a Go thread... but I'll leave it here anyway.
I think you may have the causality swapped here; I think <algorithm> looks the way it does because they limited themselves to things that could be easily expressed with operators --- for a long time, to the detriment of the language; see: STL associative containers, operator<, and the longstanding lack of a standard hash table.
Josuttis claims that hash tables didn't make it in C++98 due to lack of time.
Type 2 defines: .plus(x)
Type 3 defines: .vector_add(x)
Now, implement a function 'average' that can work on any of these three types.
If you can get everyone in the world to agree to a convention of how to express 'addition', then there is no difference, except we already have a convention for 500 years, and it is the '+' operator. Why you think the '+' operator is confusing but .plus() is not, that is what confuses me.
Or how about 'minimum'. In C people end up writing a minimum C macro, because there's not even a way to write one function that works on int, long, float! What a sad world that is, where you have to meta program to implement min(x,y).
Operator overloading works best when being used in domains where the original constraints hold; for instance, adding a matrix to a matrix is mostly the same as adding two numbers, though if your type system can't enforce that the matrices are the same size at compile time, you still added the ability for + to throw an exception, which it will never do with ints. Operator overloading got its bad name from cases where people were overloading the operators to do something entirely unlike what the original operator did, causing a mismatch between the user's expectations and what it actually did and therefore bugs.
Operator overloading isn't really "right" or "wrong" per se, but it's probably a bad idea for anything that isn't able to fully implement the contract of the operator, including "associativity", "no side effects", whether exceptions can be thrown, etc.
If you read carefully, you'll generally see operator overloading arguments have two groups talking past each other, one cursing things like C++ streams that basically overloaded the operators in a meaningless way for nominal convenience that causes a lot of long-term headaches, and the other praising the benefits of overloading for math, since math is the big case where it works correctly.
Back on topic, Go correctly does not have operator overloading because Go's authors, as near as I can tell, have no intention of Go being good for mathematics.
Operators are not special here. What you are saying is basically "don't claim to implement the interface if you didn't implement it".
This exact problem exists widely in, for example, Java. How often do you see a Java object where someone overrode equals() but not hashCode()? I've seen that problem vastly more often than anyone doing something crazy with operators.
Not quite true, the language could implement checked over- or under-flows (I believe Swift does)
I am one of the apparently about ten people who thinks that should be the universal default. The vast bulk of people disagree, and I was trying not to poke the sleeping dog. :)
There's a handful of us, a handful!
FWIW Rust checks for overflow in debug mode, and while that's elided by default when compiling with optimisations it can be re-enabled with a -Z flag:
> rustc test.rs
> ./test
thread '<main>' panicked at 'arithmetic operation overflowed', test.rs:4
> rustc -O test.rs
> ./test
Overflowed!
> rustc -O -Z force-overflow-checks=on test.rs
> ./test
thread '<main>' panicked at 'arithmetic operation overflowed', test.rs:4
And of course some languages bypass the whole thing by automatically promoting to dynamically sized integrals.I was lumping this in under the general category of the "Go has no generics" issue; without generics, Go can't do this anyway (and the closest solution you would have would be to define an interface Algebraic that specifies functions algebraic types need to support, then implement operations like min and average atop those interfaces). I'm still of the personal opinion that (as other commenters noted) what you gain in being able to define operator+ you lose in boost developers getting clever and implementing operator/= on paths; even if we had generics, I'd personally find a Go-like solution of declaring the function package an algebraic type had to support (via an Interface) preferable.
I'm not convinced this is such a large problem that its solution is worth the myriad downsides that operator overloading is chained to. There's lots of schoolbook examples like this, but I've almost never seen operator overloading used well in practice. There's a few examples, like boost shared pointers are somewhat easier to read, but they're few and far between
I do graphics programming and I literally rely on it all the time.
Hint: Things like "they can be abused" or "you can do crazy things like have + return a dot product" are not unique to operators. I can very easily define .clone() in Java to return a dot product as well, or have .equals() do in-place addition.
This is true of methods too. If you're searching for vector addition and you grep for "Add()", you're also going to have a bad time. To have a reliable code indexing scheme, you need typechecking/name resolution information, and once you have that you can easily handle operator overloading as well.
This is likely a short-term reality, but it's the current reality.
Though I've always found GitHub's code search to be less than useful in general. I think it doesn't really make sense to not have overloading because GitHub doesn't support semantic indexing in 2015, especially since you can easily just not use GitHub for code search.
- Queries <= 3 characters in length
- Certain common characters are totally not supported in queries - quotes, etc.
- Forks cannot be searched.
I always just clone and use a proper search tool if I know what I am looking for is in a specific codebase.
However "working as intended" I think is also the response of Go to their lack-of-generics, which I think is kind of crap.
And while I'm not particularly familiar with Go, I don't see how you can get a feature set equivalent to a simple implementation like Java's out of Go interfaces ("you can just cast" is not a good answer).
This is what Java designers thought as well. See where this has led them.
How is this not a design choice?
The response I've overwhelmingly observed from qualified voices isn't "boo generics!" but "generics are terribly difficult to get right, and we want to know more about Go's niche before making any design decisions in that realm".
I suspect you're reading a lot of drivel from web programmers who discovered net/http last Thursday. It's hard not to be frustrated with the internet, I concede...
Completely irrelevant; the argument isn't "generics are hard [full stop]". The argument is "generics are hard" AND "we don't know Go's niche well enough to commit to any specific approach".
What I can say is that I recently interviewed a Google engineer who was conducting a survey of Go's usage, so it seems they're at least studying the question.
Where the hell did you get that from?
So I think operator overloading can be exploited pretty horribly but can also make code far more intuitive. It's certainly not something that can't be worked around but I always err on the side of letting it happen and the community can direct people to using them most properly.
A difference is that operators tend to have semantic baggage, which can be a huge boon when using operator overloading for e.g. calculations. C++ has demonstrated that it was a very bad idea to overload operator against their semantic baggage, but the lesson to draw from it is "don't do that" not "operator overloading is the devil".
Very much working as intended, I believe. Experience from languages that support those features has shown that what we gain in the very few situations where those extensions make sense (such as defining mathematical operations on vectors using the same symbols that are used in vector mathematics), we lose in too many developers thinking they have a clever shortcut that an existing operator would be perfect for, to the detriment of readability and comprehensibility.
This is also the era of code-analysis-by-search-engine, and operator overloading harms that feature significantly. If I need to find all instances of vector addition in my code and I'm searching for '+', I'm going to have a bad time.