Hacker News new | ask | show | jobs
by stouset 3 days ago
All this just to prevent people from using + - * / and ^. Why?
2 comments

Andrew talks about it because it introduces hidden control flow where you're expecting simple operators. In Zig anything that deals with control flow is a keyword (including short circuiting and, which is `and` instead of `&&`).

I'd argue though that the real disadvantage to having overloadable arithmetic is that you're limited to one implementation. This is actually my biggest beef with Rust, namely traits/type classes. It locks you into a single implementation when you may want to do something different based on the context. Zig pushes the dispatch decision to the callsite, not a trait subsystem (see how Zig implements hash mays for example). So I'd personally prefer to use a DSL, since it lets me specify what type of dispatch to use.

Overloadable operators are not an instance of hidden control flow. Overloadable operators represent a user-defined function call, and thus can't influence control flow any more than a regular function. And if regular functions can't do anything weird to control flow (e.g. if your language already lacks exceptions (or even weirder things like Ruby-style procs)), then overloadable operators can't either.

> It locks you into a single implementation when you may want to do something different based on the context.

If you want differing behavior in a certain context, and if you don't want to use a different method to make the differing behavior explicit (e.g. the `wrapping_add` methods that Rust provides on numeric types), then you can use a different type for that context, e.g. the `std::num::Wrapping` type that Rust provides.

> Overloadable operators are not an instance of hidden control flow.

In general perhaps not, but in Zig it definitely does. Zig considers calling a function to change control flow, because it's no longer just an operator but something that can cause side effects, includinh mutating in place. Perhaps control flow isn't the right term, maybe non-trivial would be better?

With regard to wrappers, I personally find them ugly since 1. They bring in indirection, and I have a personal vendetta against unnecessary indirection, 2. Wrapping doesn't compose well and is a pain to shephard between representations, 3. It's harder to make a function generic across different representations, and 4. Wrappers often don't re-export everything available to their underlying value.

> Perhaps control flow isn't the right term, maybe non-trivial would be better?

Indeed, there are plenty of valid reasons to be wary of operator overloading, such as the risk that someone might insert a network call into your vector addition. There's some precedence from C++ in calling an operator invocation "trivial" when it hasn't been user-defined, in general I might go further and say that a good overloaded operator is "well-behaved" when it not only has a non-surprising implementation (e.g. no side-effects) but also its function is congruent with the specific chosen operator (so no overloading bitshift for iostreams).

I’m not advocating this, but it is worth observing that it is yet another problem one could attempt to address with dependency injection, similar to io and allocators.
It's appealing to people who want to understand and control everything they're doing. When I'm using pandas or SQLAlchemy, I have no idea what the code is actually doing. Most people don't care about such implementation details, but some people do.