Why aren’t user-defined operators more common? [closed]

One feature I miss in from functional languages is the idea that operators are just functions, so adding a custom operator is often as simple as adding a function. Many procedural languages allow operator overloads, so in some sense operators are still functions (this is very true in D where the operator is passed as a string in a template parameter).

It seems that where operator overloading is allowed, it is often trivial to add additional, custom operators. I found this blog post, which argues that custom operators don’t work nicely with infix notation because of precedence rules, but the author gives several solutions to this problem.

I looked around and couldn’t find any procedural languages that support custom operators in the language. There are hacks (such as macros in C++), but that’s hardly the same as language support.

Since this feature is pretty trivial to implement, why isn’t it more common?

I understand that it can lead to some ugly code, but that hasn’t stopped language designers in the past from adding useful features that can be easily abused (macros, ternary operator, unsafe pointers).

Actual use cases:

  • Implement missing operators (e.g. Lua doesn’t have bitwise operators)
  • Mimic D’s ~ (array concatenation)
  • DSLs
  • Use | as Unix pipe-style syntax sugar (using coroutines/generators)

I’m also interested in languages that do allow custom operators, but I’m more interested in why it has been excluded. I thought about forking a scripting language to add user-defined operators, but stopped myself when I realized that I haven’t seen it anywhere, so there’s probably a good reason why language designers smarter than me haven’t allowed it.

10

There are two diametrically opposed schools of thought in programming language design. One is that programmers write better code with fewer restrictions, and the other is that they write better code with more restrictions. In my opinion, the reality is that good experienced programmers flourish with fewer restrictions, but that restrictions can benefit the code quality of beginners.

User-defined operators can make for very elegant code in experienced hands, and utterly awful code by a beginner. So whether your language includes them or not depends on your language designer’s school of thought.

8

Given a choice between concatenating arrays with ~ or with “myArray.Concat(secondArray)”, I would probably prefer the latter. Why? Because ~ is a completely meaningless character that only has its meaning – that of array concatenation – given in the specific project where it was written.

Basically, as you said, operators are no different from methods. But while methods can be given readable, comprehensible names that add to the understanding of the code flow, operators are opaque and situational.

This is why I also don’t like PHP’s . operator (string concatenation) or most of the operators in Haskell or OCaml, though in this case, some universally accepted standards are emerging for functional languages.

14

Since this feature is pretty trivial to implement, why isn’t it more common?

Your premise is wrong. It’s not “pretty trivial to implement”. In fact, it brings a bag of problems.

Let’s have a look at the suggested “solutions” in the post:

  • No precedence. The author himself says “Not using precedence rules is simply not an option.”
  • Semantic aware parsing. Like the article says, this would require the compiler to have a lot of semantic knowledge. The article doesn’t actually offer a solution for this and let me tell you, this simply isn’t trivial. Compilers are designed as a trade-off between power and complexity. In particular, the author mentions a pre-parsing step to collect the relevant information, but pre-parsing is inefficient and compilers strive very hard to minimise parsing passes.
  • No custom infix operators. Well, that’s not a solution.
  • Hybrid solution. This solution carries many (but not all) of the disadvantages of semantic aware parsing. In particular, since the compiler has to treat unknown tokens as potentially representing custom operators, it often cannot produce meaningful error messages. It also may require the definition of said operator to proceed with parsing (to collect type information etc.), once again necessitating an additional parsing pass.

All in all, this is an expensive feature to implement, both in terms of parser complexity and in terms of performance, and it’s not clear that it would bring a lot of benefits. Sure, there are some benefits to the ability of defining new operators but even those are contentious (just look at the other answers arguing that having new operators isn’t a good thing).

9

Let’s ignore the whole “operators get abused to harm readability” argument for the moment, and focus on the language design implications.

Infix operators have more issues than simple precedence rules (though to be blunt, the link you reference trivializes the impact of that design decision). One is conflict resolution: what happens when you define a.operator+(b) and b.operator+(a)? Preferring one over the other leads to breaking the expected commutative property of that operator. Throwing an error can lead to modules that would otherwise work become broken once together. What happens when you start throwing derived types into the mix?

The fact of the matter is that operators are not just functions. Functions either stand alone or are owned by their class, which provides a clear preference on which parameter (if any) owns the polymorphic dispatch.

And that ignores the various packaging and resolution problems that arise from operators. The reason languages designers (by and large) limit infix operator definition is because it creates a pile of problems for the language while providing debatable benefit.

And frankly, because they’re not trivial to implement.

9

I think you’d be surprised how often operator overloads are implemented in some form. But they’re not commonly used in a lot of communities.

Why use ~ to concatenate to an array? Why not use << like Ruby does? Because the programmers you work with are probably not Ruby programmers. Or D programmers. So what do they do when they come across your code? They have to go and look up what the symbol means.

I used to work with a very good C# developer who also had a taste for functional languages. Out of the blue, he started introducing monads to C# by way of extension methods and using standard monad terminology. No one could dispute that some of his code was terser and even more readable once you knew what it meant, but it did mean that everyone had to learn monad terminology before the code made sense.

Fair enough, you think? It was only a small team. Personally, I disagree. Every new developer was destined to be confused by this terminology. Do we not have enough problems learning a new domain?

On the other hand, I will happily use the ?? operator in C# because I expect other C# developers to know what it is, but I wouldn’t overload it into a language that didn’t support it by default.

6

I can think of a few reasons:

  • They aren’t trivial to implement – allowing arbitrary custom operators can make your compiler much more complex, especially if you allow user-defined precedence, fixity and arity rules. If simplicity is a virtue, then operator overloading is taking you away from good language design.
  • They get abused – mostly by coders who think it is “cool” to redefine operators and start redefining them for all sorts of custom classes. Before long, your code is littered with a load of customised symbols that nobody else can read or understand because the operators don’t follow the conventional well-understood rules. I don’t buy the “DSL” argument, unless your DSL happens to be a subset of mathematics 🙂
  • They hurt readability and maintainability – if operators are regularly overridden, it can become hard to spot when this facility is being used, and coders are forced to continually ask themselves what an operator is doing. It is much better to give meaningful function names. Typing a few extra character is cheap, long term maintenance problems are expensive.
  • They can break implicit performance expectations. For example, I’d normally expect lookup of an element in an array to be O(1). But with operator overloading, someobject[i] could easily be an O(n) operation depending on the implementation of the indexing operator.

In reality, there are very few cases where operator overloading has justifiable uses compared to just using regular functions. A legitimate example might be designing a complex number class for use by mathematicians, who understand the well-understood ways that mathematical operators are defined for complex numbers. But this really isn’t a very common case.

Some interesting cases to consider:

  • Lisps: in general don’t distinguish at all between operators and functions – + is just a regular function. You can define functions however you like (typically there is a way of defining them in separate namespaces to avoid conflict with the built-in +), including operators. But there is a cultural tendency to use meaningful function names, so this doesn’t get abused much. Also, in Lisp prefix notation tends to get used exclusively, so there is less value in the “syntactical sugar” that operator overloads provide.
  • Java – disallows operator overloading. This is occasionally annoying (for stuff like the Complex number case) but on average it’s probably the right design decision for Java which is intended as a simple, general-purpose OOP langauge. Java code is actually quite easy for low/medium-skilled developers to maintain as a result of this simplicity.
  • C++ has very sophisticated operator overloading. Sometimes this gets abused (cout << "Hello World!" anyone?) but the approach makes sense given C++’s positioning as a complex language that enables high level programming while still allowing you to get very close to the metal for performance, so you can e.g. write a Complex number class that behaves exactly as you want without compromising performance. It is understood that it is your own responsibility if you shoot yourself in the foot.

Since this feature is pretty trivial to implement, why isn’t it more common?

It isn’t trivial to implement (unless trivially implemented). It also doesn’t get you very much, even if implemented ideally: the readability gains from terseness are offset by the readability losses from unfamiliarity and opacity.
In short, it’s uncommon because it’s not usually worth the developers’ or the users’ time.

That said, I can think of three languages that do it, and they do it in different ways:

  • Racket, a scheme, when it isn’t being all S-expression-y allows and expects you to write a what amounts to parser for any syntax you want to extend (and provides useful hooks to make this tractable).
  • Haskell, a purely function programming language, allows defining any operator that consists solely of punctuation, and allows you to provide a fixity level (10 available) and an associativity. Ternary etc. operators can be created out of binary operators and higher order functions.
  • Agda, a dependently typed programming language, is extremely flexible with operators (paper here) allowing both if-then and if-then-else to be defined as operators in the same program, but its lexer, parser, and evaluator are all strongly coupled as a result.

1

One of the main reasons custom operators are discouraged is because then any operator can mean/can do anything.

For example cstream‘s much criticized left shift overload.

When a language allows operator overloads, there is generally an encouragement to keep the operator behavior similar to the base behavior to avoid confusion.

Also user defined operators makes the parsing much more difficult, especially when there is also custom preference rules.

5

We do not use user-defined operators for the same reason we do not use user-defined words. No one would call their function “sworp”. The only way to convey your thought to other person is to use shared language. And that means both words and signs (operators) must be known to the society for whom you are writing your code.

Therefore the operators you see in use in programming languages are the ones we’ve been taught in school (arithmetic) or the ones that have been established in programming community, like say boolean operators.

4

You’re fighting against two things here:

  1. Why do operators exist in languages in the first place?
  2. What is the virtue of operators over functions/methods?

In most languages, operators are not really implemented as simple functions. They might have some function scaffolding, but the compiler/runtime is explicitly aware of their semantic meaning and how to translate them efficiently in to machine code. This is much more true even as compared to built-in functions (which is why most implementations also don’t include all the function call overhead in their implementation). Most operators are higher level abstractions on primitive instructions found in CPUs (which is partly why most operators are arithmetic, boolean, or bitwise). You could model them as “special” functions (call them “primitives” or “builtins” or “native” or whatever), but to do that generically requires a very robust set of semantics for defining such special functions. The alternative is to have built-in operators that semantically look like user defined operators, but which otherwise invoke special paths in the compiler. That runs afoul of the answer to the second question…

Aside from the machine translation issue I mentioned above, at a syntactical level operators aren’t really different from functions. They’re distinguishing characteristics tend to be that they are terse and symbolic, which hints at a significant additional characteristic they must have to be useful: they must have broadly understood meaning/semantics to developers. Short symbols don’t convey much meaning unless it is short hand for a set of semantics that are already understood. That makes user defined operators inherently unhelpful, as by their very nature they aren’t so broadly understood. They make as much sense as one or two letter function names.

C++’s operator overloads provides fertile ground for examining this. Most operator overload “abuse” comes in the form of overloads that break some of the semantic contract that is broadly understood (a classic example is an overload of operator+ such that a + b != b + a, or where + modifies either of its operands).

If you look at Smalltalk, which does allow operator overloading and user defined operators, you can see how a language might go about doing it, and how useful it would be. In Smalltalk, operators are merely methods with different syntactical properties (namely, they are encoded as infix binary). The language uses “primitive methods” for special accelerated operators and methods. You find that few if any user defined operators are created, and when they are, they tend not to get used as much as the author probably intended for them to be used. Even the equivalent of an operator overload is rare, because it is mostly a net loss to define a new function as an operator instead of a method, as the latter allows for an expression of the semantics of the function.

As to languages that do support such overloading: Scala does, actually in a much cleaner and better way can C++. Most characters can be used in function names, so you can define operators like !+*=++ , if you like. There is built-in support for infix (for all functions taking one argument). I think you can define the associativity of such functions, too. You can’t however, define the precedence (only with ugly tricks, see here).

One thing that hasn’t been mentioned yet is the case of Smalltalk, where everything (including operators) is a message send. “Operators” like +, | and so on are actually unary methods.

All methods can be overridden, so a + b means integer addition if a and b are both integers, and means vector addition if they’re both OrderedCollections.

There are no precedence rules, since these are just method calls. This has an important implication for standard mathematical notation: 3 + 4 * 5 means (3 + 4) * 5, not 3 + (4 * 5).

(This is a major stumbling block to Smalltalk newbies. Breaking mathematics rules removes a special case, so that all code evaluation proceeds uniformly left to right, making the language that much simpler.)

I’ve always found operator overloads in C++ to be a convenient shortcut for a single-developer team, but which causes all sorts of confusion in the long term simply because the method calls are being “hidden” in a way that isn’t easy for tools like doxygen to pick apart, and people need to understand the idioms in order to make use of them properly.

Sometimes it’s a lot harder to make sense of than you’d expect, even. Once upon a time, in a large cross-platform C++ project I decided it’d be a good idea to normalize the way that paths were built by creating a FilePath object (similar to Java’s File object), that would have operator/ used to concatenate another path part on it (so you could do something like File::getHomeDir()/"foo"/"bar" and it would do the right thing on all of our supported platforms). Everyone who saw it would essentially say, “What the hell? String division? … Oh, that’s cute, but I don’t trust it to do the right thing.”

Similarly, there’s a lot of cases in graphics programming or other areas where vector/matrix math happens a lot where it’s tempting to do things like Matrix*Matrix, Vector*Vector (dot), Vector%Vector (cross), Matrix*Vector (matrix transform), Matrix^Vector (special-case matrix transform ignoring the homogenous coordinate – useful for surface normals), and so on, but while it saves a bit of parsing time for the person who wrote the vector math library, it only ends up confusing the issue more for others. It just isn’t worth it.

Operator overloads are a bad idea for the same reason that method overloads are a bad idea: the same symbol on the screen would have different meanings depending upon what is around it. This makes it more difficult for casual reading.

Since readability is a critical aspect of maintainability, you should always avoid overloading (except in some very special cases). It is far better for each symbol (whether operator or alphanumeric identifier) to have a unique meaning that stands on its own.

To illustrate: when reading unfamiliar code, if you encounter a new alphanum identifier that you don’t know, at least you have the advantage that you know you don’t know it. You can then go look it up. If, however, you see a common identifier or operator that you know the meaning of, you are far less likely to notice that it actually has been overloaded to have a completely different meaning. To know what operators have been overloaded (in a code base that made widespread use of overloading), you would need a working knowledge of the complete code, even if you only want to read a small part of it. This would make it hard to bring new developers up to speed on that code, and impossible to bring people in for a small job. This may be good for programmer job security, but if you are responsible for the success of the code base, you should avoid this practice at all costs.

Because operators are small in size, overloading operators would allow more dense code, but making code dense is not a real benefit. A line with twice the logic takes twice as long to read. The compiler does not care. The only issue is human readability. Since making code compact does not enhance readability, there is no real benefit to compactness. Go ahead and take the space, and give unique operations a unique identifier, and your code will be more successful in the long run.

1

Technical difficulties to handle precedence and complex parsing set aside, I think there are a few aspects of what a programming language is that has to be considered.

Operators are typically short logical constructs that are well defined and documented in the core language (compare, assign..). They are also typically hard to understand without documentation (compare a^b with xor(a,b) for instance). There are a rather limited number of operators that actually might make sense in normal programming (>,<, =,+ etc..).

My idea is that it’s better to stick to a set of well defined operators in a language – then allow operator overloading of those operators (given a soft recommendation that the operators should do the same thing, but with a custom data type).

Your use cases of ~ and | would actually be possible with simple operator overloading (C#,C++, etc.). DSL is a valid usage area, but probably one of the only valid areas (from my point of view). I, however, think that there are better tools to create new languages within. Executing a true DSL language within another language is not that difficult by using any of those compiler-compilers tools. Same goes for the “extend LUA argument”. A language is most likely defined primarily to solve problems in a specific way, not to be a foundation for sub-languages (exceptions exists).

Another factor for it is that it’s not always straight forward to define an operation with the available operators. I mean, yes, for any kind of number, the ‘*’ operator can make sense, and are usually implemented in the language or in existing modules. But in the case of the typical complex classes that you need to define (things like ShipingAddress, WindowManager, ObjectDimensions, PlayerCharacter, etc) that behaviour is not clear… What does mean to add or subtract a number to an Address? Multiply two Addresses?

Sure, you can define that adding a string to an ShippingAddress class means a custom operation like “replace line 1 in address”(instead of “setLine1” function) and adding a number is “replace zip code” (instead of “setZipCode”), but then the code is not very readable and confusing. We typically think that operator are used in basic types/classes, as their behaviour is intuitive, clear and consistent (once you are familiar with the language, at least). Think in types like Integer, String, ComplexNumbers, etc.

So, even if defining operators can be very useful in some specific cases, their real-world implementation is quite limited, as the 99% of the cases where that will be a clear win are already implemented in the basic language package.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật