It wasn't a mistake, just a deliberate compromise.
My goal was a very small, simple, but flexible language. It needed to be pretty tiny to be embeddable in lots of applications. (And pragmatically, so that I could design and build the whole language myself.)
Single dispatch works really well for, like 90% of the kinds of operations you want to perform in programs. It reads really well syntactically and avoids having to namespace every function with its argument type like you see in C and Scheme (hash-remove!, dict-remove!, set-remove!, etc.). And it does this without needing types or static resolution. Also, it's simple to compile to something fairly efficient at runtime using a vtable-like structure.
It just sucks for binary operators. But coming up with a better solution for that requires something like:
* Static types and static dispatch like C++, C#, etc. do.
* Multimethods and multiple dispatch.
* Double dispatch like Python.
* Hacking something special into the language for operators like not allowing them to be overloaded and just having a fixed set of types they apply to.
Static types and multimethods are a huge jump in complexity. Python's approach is really slow. Special casing operators makes me sad.
So I just took a relatively small expressiveness hit and decided that, yes, operators aren't symmetric. In practice, it's mostly fine.
Sorry for focusing on such a small part of your comment. I’m learning about language design (as much as I can) and I don’t really understand what you mean by “double dispatch like Python”.
I think (maybe soon thought) that Python has single dispatch. Since you’ve invented languages and work on them I’m pretty much 100% sure I’m wrong and would love to learn why.
I read https://en.m.wikipedia.org/wiki/Multiple_dispatch and came to the conclusion Python has “single dispatch polymorphism” because the method resolution is based on the type of the calling object dynamically at runtime and there is no method signature overloading, which means the argument type(s) doesn’t play a part in picking/resolving the method to be called.
If you have time, do you mind explaining or pointing me to some resources?
Yeah - `__add__` then `__radd__` isn't exactly double dispatch, but it's close. (There are corner cases involving inheritance where double-dispatch will work correctly but Python's approach will fail to pick the most-specific method.)
It is a mistake, because if you can't do "1.0 - z" or "0.5 * A", I don't know why you have operator overloading at all. What types with useful binary operators don't need to interact with scalars? Sets? Sequences? There aren't many math-y things that don't need numbers. The absence of complex values and matrices makes me sad, and I can list a dozen more on top of those.
Yes, static typing would be a huge change, but I think you're greatly exaggerating the complexity of multimethods for (only) binary operators. You don't want to do it, which is fine. It's your language, and you don't owe anyone anything, but be honest about your rationale.
You've already got a failure path to report errors when the types don't work, so falling back to a single global table of "first_type x second_type => function" is not more "huge" than anything else. You don't need scoping for operators like this, so it's literally one table. You can see the code in your head as you read this. I'm sure you could fill in the details or imagine some other simple implementation if you wanted to.
> I don't know why you have operator overloading at all.
Wren doesn't have "operator overloading". There is no separate feature in addition to the normal operator support that also lets you overload them. Wren just uses single dispatch for infix operators. Even the "built in" operator behavior like addition on numbers is simply pre-defined methods on the Number class. Like Smalltalk (which Wren is heavily inspired by), everything is just a method call and there is little "special" behavior for primitives.
My goal was a very small, simple, but flexible language. It needed to be pretty tiny to be embeddable in lots of applications. (And pragmatically, so that I could design and build the whole language myself.)
Single dispatch works really well for, like 90% of the kinds of operations you want to perform in programs. It reads really well syntactically and avoids having to namespace every function with its argument type like you see in C and Scheme (hash-remove!, dict-remove!, set-remove!, etc.). And it does this without needing types or static resolution. Also, it's simple to compile to something fairly efficient at runtime using a vtable-like structure.
It just sucks for binary operators. But coming up with a better solution for that requires something like:
* Static types and static dispatch like C++, C#, etc. do.
* Multimethods and multiple dispatch.
* Double dispatch like Python.
* Hacking something special into the language for operators like not allowing them to be overloaded and just having a fixed set of types they apply to.
Static types and multimethods are a huge jump in complexity. Python's approach is really slow. Special casing operators makes me sad.
So I just took a relatively small expressiveness hit and decided that, yes, operators aren't symmetric. In practice, it's mostly fine.