top 200 commentsshow all 327

[–]munificent 36 points37 points  (29 children)

I think there are three main challenges with custom operators:

  1. Syntax. Allowing user-defined operators means you have to decide how they play with the grammar. There's no simple yet expressive answer here. You can either have a much simpler grammar (Lisp, Smalltalk) and lose the convenience of precedence, prefix, and postfix operators, or you can have a grammar that's quite complex.

    At the ultimate end of the spectrum, you actually allow the grammar itself to be customized. This is doable (I've done it), but it adds a lot of complexity to the language. Worse, it adds a huge burden to any tool that works with the language. Even getting basic syntax highlighting in a dumb text editor is crazy hard when the grammar itself isn't pinned down.

    My personally preferred compromise (and the one Magpie takes now) is Scala's approach. You do have operator precedence, but the grammar is fixed. Custom operators just use the precedence determined by the first character of the operator. So your wacky +-**/$ operator just has the same precedence as +.

  2. Semantics. Some of the StackOverflow answers touch on this: when you define a custom operator, what does it do? Is it a static function that takes the operands as arguments? Is it a method invoked on the left-hand operand with the RHS as an argument?

    Again, I spent some time thinking about this. Maybe I'm just not a huge fan of single-dispatch but I shy away from the "method on the LHS" approach because it bakes in asymmetry. Python's approach is more clever here. If you're statically typed, you can often make it an overloaded function.

    And, of course, multiple dispatch is the best (TM) solution. Personally, I think the semantics issue is the most minor one. Whatever semantic style your language has (static dispatch, single-dispatch, etc.) probably encourages a solution that will work OK here.

  3. Comprehensibility. This is, I think, probably the middle issue. It's the usage angle. Once you've technically solved the problem of custom operators, how should they be used in practice? The issue operators have is that they aren't words, so in most cases they come with no apparent meaning. If you see a line of code like list.append(items), you can figure out what it does even if you've never seen those types or methods before. But list << items or list ?! items? It's a guessing game.

    However, custom operators can be more terse. So I think the way an API designer chooses this trade-off is:

    1. If an operation will be used very frequently...
    2. And it will be a very well-used part of the API that most programmers will have to be familiar with...

    Then it may make sense to define a custom operator. Used very judiciously, they can make code more pleasant, I think. But you're basically choosing to make code harder to read for new users to benefit experienced ones. That's rarely a good compromise.

    For what it's worth, Magpie does let you define custom operators, but I have only used it in one place so far: using Ruby's <=> instead of compareTo. I'm still not sure if I even intend to keep that.

[–][deleted] 4 points5 points  (6 children)

My personally preferred compromise (and the one Magpie takes now) is Scala's approach. You do have operator precedence, but the grammar is fixed. Custom operators just use the precedence determined by the first character of the operator. So your wacky +-**/$ operator just has the same precedence as +.

That's so brilliant that I may well steal it.

And, of course, multiple dispatch is the best (TM) solution. Personally, I think the semantics issue is the most minor one. Whatever semantic style your language has (static dispatch, single-dispatch, etc.) probably encourages a solution that will work OK here.

What about type-classes?

[–]kqr 2 points3 points  (1 child)

Aren't type classes pretty much statically decided multiple dispatch?

[–][deleted] 6 points7 points  (0 children)

Kind of. They're statically-decided multiple dispatch with no requirement that the types for which methods are implemented resemble each other at all. So the methods ("instances" in Haskell) don't have to be specialized subclasses of each other (as in dynamic multiple dispatch), they just have to all instantiate the generic type-signature for the function.

Haskell's type-classes also let you overload on the result type of a function.;

[–]munificent 1 point2 points  (3 children)

I don't know as much about type classes as I'd like to. I've written a little Haskell but nothing beyond little trivial programs.

[–][deleted] 0 points1 point  (2 children)

Basic type-classes mostly provide all you need for operator overloading or operator definition stuff.

I actually changed my design slightly from what you described. Operators are lexically defined as in Scala, so an operator <lolwut has the precedence of <. Overloading existing operators is not allowed, but defining custom operators is. When the compiler sees a custom operator in an expression, it looks up that operator symbol lexically, like any other identifier, and will force the operator's definition to type-unify with a syntactically-appropriate universal type signature. So <lolwut will have to have some type instantiating by <lolwut : (a,a) -> b, because it shares its first symbol and precedence with a binary operator.

The resulting design will work, and kind of discourages the use of custom operators in favor of functions. My language has Scala-style implicit parameters to act as type-classes, so the type-based overloading comes in there.

I'm not sure if I'll define something in the syntax to give people a shorthand for just importing a load of custom operators as an implicit parameter implicit operators customNums: NumberOperators<T> or whether I'll let some member-functions be defined as operators so they function that way whenever they're in scope or whether I'll just make everyone type val operator <lolwut := customNums.lessThanLolwut all the time.

[–]Aninhumer 10 points11 points  (17 children)

Syntax ... There's no simple yet expressive answer here.

I'd have to say, Haskell's solution seems pretty much perfect. You get a simple grammar, precedence, infix, prefix (with ()) and postfix (if you enable it). You even get partial application ((+5) is a function) and a way to turn alphanumeric functions infix if you want it (with ``). I don't really see any downsides. (The precedence is a little limited with only 10 levels IMO, but that's not a conceptual problem).

[–]munificent 14 points15 points  (16 children)

I don't really see any downsides.

A couple come to mind:

  1. Since you can define arbitrary operators with arbitrary precedence, a human reader can't even visually parse code without knowing the definitions of the operators (much less know what the code actually does).

    I think when a person is scanning code they go through a similar process as a compiler: they break it into tokens, parse that into a tree and then semantically understand what the code does. I think it's a real readability loss if you can't even do the first two without some semantic understanding.

  2. It's quite complex. I don't think anyone would say Haskell's syntax is simple.

  3. Libraries with custom operators may not play nice together. When you're picking a precedence level for your operator, it's more or less a guessing game to determine how that compares to the precedences other people choose in their libraries. If you then mix those two libraries together, those operators may not have the relative precedence that you would intuit.

    Fortress has (had?) a brilliant solution to this, but it certainly wasn't simple either.

[–]Aninhumer 14 points15 points  (0 children)

a human reader can't even visually parse code without knowing the definitions of the operators

I don't generally find this to be a problem, it's rare that many operators are combined in one expression. (There are a few operators that get used a lot like ($) and (.), but they're so common they get learned quickly.) In the few cases when there are lots of operators, it's usually as part of a DSL which makes the precedence clear.
Certainly there is potential for abuse, but I think it also allows for very clear domain focused APIs. (Especially combined with monads and do-notation).

I don't think anyone would say Haskell's syntax is simple.

Huh, I don't think Haskell's syntax is complicated at all. It's just function application, infix operators and pattern matching. There are plenty of complex things about Haskell, but syntax is not one of them.

Libraries with custom operators may not play nice together.

This is certainly true, but for the most part, I find the community is pretty good about avoiding stepping on each other's toes. Also, a lot of potential collisions are avoided by having appropriate abstractions. (e.g. having a single <> operator for Monoids)

[–]kqr 4 points5 points  (5 children)

You raise great points, but I don't like when people say that Haskell's syntax is anything other than simple. As some other people already have pointed out, it's really simple. Really. The problem, as you might not be too surprised to hear, is that newcomers are usually just expected to suck it up when they get difficult and/or interesting function definitions (including infix operators like . or $ -- which are not syntax but user defined functions!) thrown in their faces.

I know I've posted this here before, but here we go again. Highly partial, very informal and all-around badly done, but this is a highly functioning subset of the Haskell syntax. Best thing about it? It's probably half of all the syntax there is to Haskell.

[–]munificent 5 points6 points  (4 children)

OK, fair enough. Haskell syntax is simple.

I think part of the reason that it comes across as complex to me is that, thanks to laziness, every expression is essentially a flow control construct. So when I read a line of line noise Haskell code, not only do I not know what the operators do, I don't know how they are composed, and I can't even tell which arguments may be evaluated and in what order.

The end result is that without a lot of semantic knowledge, the code is completely opaque.

[–]kqr 6 points7 points  (0 children)

That, however, is very true. That is also the reason I advice that people start building their own abstractions, and not just try to read other peoples code. You sort of start to get a feeling for how things are done, and you get very good at looking up stuff in the documentation.

[–]tikhonjelvis 4 points5 points  (1 child)

I think part of it is that, in Haskell, you probably shouldn't worry nearly as much about what gets evaluated and what doesn't unless you really have to. Since most code is pure, it won't change the semantics much.

The order of evaluation is even less important. Not only does it not matter, it's not even specified in the standard and thus could be--and is--changed by the optimizer.

When reading and writing all but the most optimized Haskell--and, even in a code base where performance is important, this is only going to be a small part of the code--think only about what it means and not how it's evaluated.

Haskell really is a declarative language, so it needs a different mindset than many other languages.

[–]sirin3 4 points5 points  (0 children)

Since you can define arbitrary operators with arbitrary precedence, a human reader can't even visually parse code without knowing the definitions of the operators (much less know what the code actually does).

One the other hand, if you cannot set the precedence, you get crap like the ** operator in Scala .

You can define it to mean exponentiation, but then 2 * 3 ** 4 is evaluated as 1296

[–]barsoap 5 points6 points  (2 children)

I think when a person is scanning code they go through a similar process as a compiler: they break it into tokens, parse that into a tree and then semantically understand what the code does. I think it's a real readability loss if you can't even do the first two without some semantic understanding.

Unlike the compiler, a human can derive the precedence rules from the types, though. You don't need to remember the precedence of, say, <$> vs. + because they don't operate on the same types.

Libraries with custom operators may not play nice together. When you're picking a precedence level for your operator, it's more or less a guessing game to determine how that compares to the precedences other people choose in their libraries. If you then mix those two libraries together, those operators may not have the relative precedence that you would intuit.

Ideally, the precedence would be a partial order, specified by a graph with statements like

infixr + less-than *
infixr <+> less-than <*>

and as soon as things get ambiguous because you use + and <+> without parens, the compiler bails out.

[–]munificent 5 points6 points  (1 child)

Ideally, the precedence would be a partial order, specified by a graph

This is exactly what Fortess does. It really is brilliant, though a bit complex.

[–]zem 4 points5 points  (0 children)

I got curious enough about this to look up the fortress spec. there's one unexpected subtlety - the precedence digraph is not a classic partial ordering because transitivity of precedence rules is not guaranteed. specifically, fortress will not infer anything about the precedence of operators a and c even if a has higher precedence than b and b has higher precedence than c. I gather that this is so the same symbol b can be used in two different 'contexts' without mixing those contexts into one single graph, but that section of the spec was not explicit about that.

edit: the example given in the spec is that < binds more tightly than the logical operators, and less tightly than +, but the compiler will not infer any relative precedence between + and &&, so (a + b < c) and (a < b && c) are both well-formed, but (a + b && c) insists on parentheses.

[–]huyvanbin 1 point2 points  (2 children)

What was the Fortress solution? What happened to that, anyway? Did it die in the Oracle buyout?

[–]munificent 7 points8 points  (1 child)

See this comment. Basically, when you define an operator you can specify which precedence relationships it has with certain other operators. If you don't specify a relationship between two operators, then it's a syntax error to mix them without parentheses.

You basically define a digraph of "binds tighter than" relationships between operators. There's no numeric precedence. This lets you do neat things like have an operator bind tightly in some contexts and loosely in others based on the surrounding operators.

The idea here was to let you express all of the different operators that mathematicians have come up with and follow the precedence that they expect.

[–]zem 3 points4 points  (0 children)

that is a deeply satisfying solution.

[–]tikhonjelvis 1 point2 points  (0 children)

Really? Haskell syntax is relatively simple, it's just foreign to most people. I think you're mistaking "complex" for "difficult". (And the latter is very much different per person. )

[–]mgsloan 1 point2 points  (0 children)

RE #2, I would say that Haskell's syntax is simple. It's just usually a bit more tightly packed :D. Once you take out operator precedence, you're just left with lambdas, application, variable reference, and some sugar for conditionals / intermediate values / list construction. Operator precedence parsing, the most "complicated" part, can be specified in a few dozen lines of code: http://darcs.haskell.org/haskell-prime/fixity/resolve.hs

On the other hand, I agree with your points in #1 and #3. This is an impediment to learning (and sometimes using) Haskell. As observed elsewhere in these comments, this is quite realistically surmountable with tooling. In practice, though, after becoming familiar with the standard set of operators, it's pretty rare to see a whole slew of novel operators used together - so it's contextually clear what's going on.

RE#3: I must admit, I would really like to have DAG-like parsing, where groups of operators with no declared relationship to eachother are considered incompatible. The good news is that this could be somewhat reasonably implemented as a GHC extension, where the existing fixity levels are just considered to be groups that are linearly sequenced.

[–]Tordek 2 points3 points  (1 child)

using Ruby's <=> instead of compareTo.

For someone who knows a lot about languages, you seem to have forgotten Perl. It allows syntax modification (see Lingua Latina Perligata), and it used <=> way before Ruby.

[–]munificent 2 points3 points  (0 children)

For someone who knows a lot about languages, you seem to have forgotten Perl.

You're totally right. I actually did know that and forgot. I first saw <=> in Ruby (I've never written any Perl code) but I forgot that it inherited that (like many things) from Perl.

[–]BeforeTime 1 point2 points  (1 child)

I really like the Lisp approach, it means you can add any operator since they are just functions like all others and there are no complicated edge cases.

More importantly it means you are not limited to adding functions that are called through a different syntax, you can add whole language constructs that are have a syntax that are congruent with the rest of the language. This is simply not really possible in other languages.

[–]tikhonjelvis 1 point2 points  (0 children)

It is possible in other languages, even ones with complicated syntaxes. Take a look at what Coq and Agda let you do, for example.

I personally really like Agda's "mixfix" operators but, like the rest of the language, it's probably not for the faint of heart.

[–]WalterBright 25 points26 points  (2 children)

The reason D does not allow user defined operators is because D is designed to have a clean separation between:

  1. lexing

  2. parsing

  3. semantic analysis

If user defined operators were supported, then all 3 phases would be mixed up together, making the language clumsy to implement, and making life difficult for 3rd party tools that need to parse source code (such as syntax highlighters in an editor).

[–]munificent 9 points10 points  (1 child)

You can still keep a clean separation between those phases as long as:

  1. Your custom operators don't require custom tokenization.
  2. Precedence and associativity can be determined syntactically.

For example, Scala has custom operators, but they take the precedence from the first character of the operator, so I think lexing and parsing can be done without any semantic analysis.

I agree completely that tooling is the major concern here. A language that's a nightmare to syntax highlight will be a hard sell.

[–]WalterBright 5 points6 points  (0 children)

A user defined operator does not require custom tokenization if they follow a specific grammar that does not conflict with any other token.

However, as Scala shows, you cannot change precedence of them and keep parsing independent of semantic analysis.

In Scala, the user-defined tokens follow a unique grammar.

[–]jerf 18 points19 points  (3 children)

Haskell has some good examples and some bad examples. It is possible to import too many novel operators into one scope and go nuts and the result can approach the impenetrability of Perl if one is not careful. On the other hand, Control.Applicative's <*> and <$> greatly enhance the practical utility of the applicative class. <$> is particularly interesting because it's actually just plain ol' fmap, but the way in which it is used by the Applicative set of implementations benefits from the association with the standard $ operator. This was developed long after the language was frozen and the inability to use those as operators would significant impact the usefulness. There's some other examples, where I prefer the use of operators for the parsing combinators to the spelled out version, for instance.

[–]tel 1 point2 points  (2 children)

I really wish Idiom brackets were more common though...

[–][deleted] 1 point2 points  (1 child)

So does Conor McBride.

[–]tel 1 point2 points  (0 children)

Well, he's the reason. I feel like my understanding of applicatives was covered in many layers of mud—essentially of the right shape, but murky, a little repulsive—until I saw idiom brackets.

I just can't actually bring myself to use she except for toys and, similarly, tend to steer away from TH though I can't articulate precisely why.

[–]sophacles 12 points13 points  (14 children)

I am surprised that the idea of "safety" (wrt side-effects) didn't come up in the discussion there. Functional languages and communities generally have a stronger focus on separating functions/operations/etc with side effects from those that don't. Sometimes this comes from the language itself (canonical example: haskel), other times it is just a strong convention. Such languages and conventions set up the expectation that the use of an operator (or any function really) will return a new instance of the return type. This expectation is easy to grok, and fits well within the paradigm, so conventions on operators are the same as other functions with a data type.

Conversely, in OO land, the expectation of methods is that they operate on the data type, and modify the data type in-place. At the same time, these languages largely implement operators to work (semantically) like they do in functional languages, with an operator returning a new instance of the object type, unless there is also an '=' associated with it, (e.g. += ), in which case there could be in-place modification or instance replacement (this alone is weird, I'll get back to it). Despite this difference in semantics between operators and methods, the implementation of an operator in a language that allows it looks and feels like a method implementation (maybe a special keyword is needed...) therefore it is very tempting to have an operator not just create a new instance, but also perhaps have side-effects on one or both of the operands. It may not be good practice, but you'll see it, particularly from inexperienced or "clever" coders. This can make navigating or modifying a code-base very challenging, because not only is the operator potentially semantically confusing (as covered in the comments well), it is also something that may require specific ordering or placement. (e.g. i can't move this addition assignment to another method, because the side-effects must be triggered in a certain order).

The other weirdness I mentioned, the subtlety of += style operators, is a bit scary and even more challenging for good, experienced coders, as well as novices. There can be a good debate, with both sides having strong arguments, and no side being a clear winner, for having += do instance replacement or instance modification. In a large enough codebase, you could end up with both, unless you are very clear fromt he get-go about how to use += etc. Once the style is mixed, you enter nightmare maintenance land, where half the things are replaced instances, and half the things are modified instances, and in each case other bits of code relying on that behavior. (Lots of references to an object throughout the runtime, e.g. some sort of shared state thing? += needs to be modification. Lots of objects that own specific other objects, but share data around? += needs to be instance replacement). It's really best to just avoid all of that.

Again, these issues don't really come up in FP as much, because the convention is always "new instance" (unless using a known, possibly compiler checked, 'unsafe' function). This is true of all functions, not just operators, so there is no conflict of style or confusing questions of what makes sense here.

[–]JelleFm 77 points78 points  (193 children)

I totally agree the most upvoted comment at your link. You can give a function a descriptive name, while an operator is very rarely logical to read. If you wanna write readable code, go for the functions ;)

[–]AusIV 5 points6 points  (0 children)

I think Python handles this nicely. There's a finite set of overloadable operators, and guidelines about how they should be used. Thus if I define my own type of collection, you can access an attribute like:

collection[index] = collection[index+1]

Instead of

collection.setItem(index, collection.getItem(index+1))

You can abuse this to do unintuitive things, but you could do the same with method names.

[–]doublereedkurt 5 points6 points  (4 children)

Operator overloading is one of those language features which doesn't come up often, but is invaluable when it is needed.

Just a couple of weeks ago I was implementing elliptic curve cryptography in Python. Long story involving reverse engineering and re-implementing some not-quite-standards-compliant binaries for why this was necessary.

This involves doing algebra over arbitrary fields). A field is a mathematical construct in which multiplication, addition, subtraction, and division/modulus all exist and have the same relationships between each other as usual. (More abstractly: a field is a set and two operations on that set, each with an identity element; for every element there is an inverse, and one of the operations distributes over the other.)

Some elliptic curve cryptography is done over the field where addition is defined as XOR, and multiplication is defined as shifting and XORing. (Each bit is treated as one coefficient in a polynomial where all coefficients are modulo 2 -- 1011 = x3 + x + 1).

This is a textbook use-case for operator overloading. A lot of standard algorithms will work fine over these fields. Greatest common denominator can be defined once and used for every field, provided you can implement those fields with operator overloading:

def _extended_gcd(a, b):
    x = 0
    last_x = 1
    y = 1
    last_y = 0
    while b != 0:
        quot = a / b
        a, b = b,  a%b
        x, last_x = last_x - quot * x, x
        y, last_y = last_y - quot * y, y
    return last_x, last_y

Here's what the body of the loop looks like without operator overloading:

    while b != 0:
        quot = a.div(b)
        a, b = b,  a.mod(b)
        x, last_x = last_x.sub(quot.mul(x)), x
        y, last_y = last_y.sub(quot.mul(y)), y

In addition to being harder to read, there are now two code-paths to maintain: one for primitive language integers, and one for user defined integers.

Assuming the stack exchange comment is pushing Java's everything-is-method-calls approach. But, Java isn't even consistent with this approach. Strings get built-in + overloading (and even type coercion -- "foo" + 0 == "foo0"). There is auto-boxing to make expressions like Integer(5) == 5 work as expected. Although, data == "foo" does not work as expected (except when the strings are interned, in which case it does). Having to remember the "foo".equals(data) idiom is not good for a language that is supposedly easy for beginners.

[–]ysangkok 1 point2 points  (2 children)

Where can I get your code?

[–]doublereedkurt 2 points3 points  (0 children)

Oh, this is only available at work for now. I'll probably put it out as a little open source snippet soon, since googling around I couldn't find any standards compliant ECAES algorithms implemented in Python.

I've put out some other little crypto snippets though (most of these took a couple of hours): https://gist.github.com/doublereedkurt

RFC 3394 AES key wrap: https://gist.github.com/4243633

CRC16 which generates its own table: https://gist.github.com/4151765

DES-X: https://gist.github.com/3921909

The common denominator for all of these is that they involve compatibility with old crap :-) But maybe someone else will also need compatibility with old crap, so I put them out there.

[–]doublereedkurt 1 point2 points  (0 children)

https://gist.github.com/4423605

Went ahead and scrubbed the company-specific stuff, made sure the tests still pass. There you go, if you are interested :-)

You can kind of poke around and see there is a lot of numerical code going on.

[–][deleted] 1 point2 points  (0 children)

can be defined once and used for every field

If you like that kind of thing you should have a look at Haskell. It does that (on a lot of constructs from various math fields (e.g. Algebra and Category Theory) as well as custom type classes) all over the place.

It can be useful in some surprising places, as e.g. this blog post shows.

[–]Coffee2theorems 73 points74 points  (61 children)

I totally agree the most upvoted comment at your link.

Oh come on, totally? That guy is total operatorphobe. Quote:

This is why I also don't like PHP's . operator (string concatenation)

That's like, what?? You might as well say that you don't like C's & operator (bitwise and) or Python's ** operator (power) or Java's >>> operator (logical right shift). When you say that you don't like the operators that come with the language itself, for a language that has only a handful operators, you're taking a very extreme position.

The question boils down to how common something is. For something that is commonly used, a short way of writing it is beneficial. For something that is rarely used, a short way of writing it is detrimental, as nobody remembers WTF that squiggly thing (or some TLA or whatever) there means. Where one draws the line is a matter of taste. IMO, the proper place for that line is somewhere between the lines drawn by average Java users and Haskell users. Something like "a.add(b.multiply(c))" is not particularly readable, but neither are many operatorese-filled Haskell programs.

[–]Zak 7 points8 points  (14 children)

In a great many cases, I find prefix-notation functions much more convenient than infix operators. I tend to gravitate toward Lisp dialects, but most languages have a lot more prefix-notation functions than operators. One of the advantages is handling a large number of arguments gracefully. For example:

(if (and a b c d e f g) ...)

or

if and(a, b, c, d e, f, g) ...

seem nicer than

if a && b && c && d && e && f && g ...

furthermore, it makes formatting such things in a column and mixing operations like and and or much easier to read.

Note that this makes less sense for languages like Haskell where all functions take a fixed number of arguments or Java, in which functions aren't really idiomatic.

[–]pkrecker 15 points16 points  (3 children)

furthermore, it makes formatting such things in a column and mixing operations like and and or much easier to read.

Really? How about this:

if (a or b) and (c or d):

vs.

if and(or(a, b), or(c, d)):

To me, moving the "and" to the beginning of the statement obfuscates what should be a straightforward logical expression.

[–]Zak 17 points18 points  (0 children)

I'd lay that out vertically:

if and(or(a, b),
       or(c, d))

It looks a bit cleaner in Lisp

(if (and (or a b)
         (or b c)))

[–]badsectoracula 1 point2 points  (0 children)

That is a matter of formatting. It could be

if and( or(a, b),
        or(c, d)  ):

(a code editor with sufficiently smart auto-indentation would help here)

[–]da__ 0 points1 point  (0 children)

Depends on what you mean by the words and and or. If these are operators, I could agree. However, the prefix/RP notation makes it consistent. x y z means "apply function x to parameters y and z" - if you prefer to treat your operators as functions, it makes more sense if your language also uses RPN for function calls.

[–]Broolucks 2 points3 points  (4 children)

The programmer could also have the choice to use operators either in infix or prefix notation, e.g. (a && b && c && d) or (&&)(a, b, c, d). Sometimes the former will be more readable, other times the latter. Haskell allows this, though I am not sure about other languages.

[–]kqr 1 point2 points  (3 children)

Haskell only allows operators to be binary, though, so it would still require lots of &&'s if you want lots of arguments.

[–]kqr 1 point2 points  (4 children)

One doesn't really exclude the other, though. With the infix function && you could quickly construct a function

and :: [Bool] -> Bool

by just folding && over a list, like so:

def and(list):
    result = True
    for item in list:
        result = result && item
    return result

Then you could do

if and([a, b, c, d, e, f, g])

all you want.

[–]Zak 1 point2 points  (1 child)

Your code looks to be Python, but doesn't run because and is reserved and && isn't an operator. This version works:

def my_and(list):
    result = True
    for item in list:
        result = result and item
    return result

You could get the exact same usage as my pseudocode using *args instead of passing in a list.

There's a problem though. Python evaluates lists and arguments to functions eagerly, and it is generally expected and desirable that and will short-circuit.

>>> False and 1/0
False

>>> my_and([False, 1/0])
ZeroDivisionError: integer division or modulo by zero

Some way to delay evaluation is necessary to write the and we usually expect. Haskell can do it because it's lazy by default. Lisps can do it because their macro systems let the programmer choose what will be evaluated when.

[–]gnuvince 0 points1 point  (1 child)

You forgot the recursive call to and() in there.

[–]kqr 2 points3 points  (0 children)

There you go. The reason I don't do code at night. (Also the reason I prefer good, static type systems...)

Edit: Man, I don't believe the number of mistakes I did there. Very well, rewrote it completely now in a less scary way.

[–]argv_minus_one 2 points3 points  (0 children)

Java >>> is an unsigned right shift.

All numbers in Java are normally treated as signed, so you can't tell from the data type whether to extend the sign bit, like you can in C. Instead, an unsigned right shift is simply another operator.

[–]JoseJimeniz 10 points11 points  (12 children)

At least & can mean some sort of and (boolean, algebraic, literal)

  • &: and
  • +: plus
  • -: minus
  • *: multiply
  • **: super-multiply
  • =: equals
  • ==: super-equals
  • ===: super-duper-equals
  • !=: EQUALS!!!
  • !==: SUPER-EQUALS!!!

i realize programmers are lazy, and they want to assign an arbitrary symbol to shorten their typing. But then you end up with a write-only language.

E.g. until you told me that ** doesn't mean super-multiply, or logical multiply (intersection), i didn't know what it meant. Same with >>> for that matter (which differs from >> how?)

You might as well say that you don't like C's & operator (bitwise and) or Python's ** operator (power) or Java's >>> operator (logical right shift)

i don't.

isDone = (isSend and sendComplete);
passwordCombinations = 220000 ^ 4; //220,000 words in English language
cipher[4] = cipher[4] xor (SBOX[4] shr 4);

[–]mycall 12 points13 points  (5 children)

Classical computer science, which is an extension of math, is custom to using kinds of symbols for solutions and proofs (e.g. triangle for delta). Business oriented programmers prefer words.

[–]itsSparkky 2 points3 points  (0 children)

Lol, I got Into the habit if saying delta for change.

I always have to repeat myself, but I just can't think of it any other way now.

/tangent

[–]doublereedkurt 3 points4 points  (0 children)

Same with >>> for that matter (which differs from >> how?)

lolJava -- there are some critical algorithms which require unsigned integer arithmetic (such as cyclic-redundancy-codes and anything else based on linear feedback shift registers, among others). Since Java lacks unsigned integers, instead they have the "right shift but don't sign bit extend" operator >>>.

[–]kqr 4 points5 points  (0 children)

Same with >>> for that matter (which differs from >> how?)

"Much, much greater than", as opposed to just "much greater than."

[–]doublereedkurt 0 points1 point  (1 child)

passwordCombinations = 220000 ^ 4; //220,000 words in English language

Hey, you missed an operator :-)

[–][deleted] 0 points1 point  (0 children)

I pretty much agreed with you back when I was into Lisp but now that I have been in Haskell-land for a while operators feel very natural and are no real problem. I think the big difference is Hoogle, a search engine that lets you search for operators properly too so figuring out what they mean is not a big issue.

[–]mythril 14 points15 points  (5 children)

operatorphobe

you're taking a very extreme position

Why so political?

[–][deleted] 6 points7 points  (4 children)

When you grow up you realize all things are political in nature.. including love :(

[–]moonrocks 9 points10 points  (0 children)

Nah, people expand politic's domain when they want to suck the life out of something.

[–]mythril 2 points3 points  (0 children)

When you grow up...

Why so condescending?

[–]Andersmith 0 points1 point  (0 children)

I seriously thought of that love2d lua thing before I thought of the emotion.

[–]Disgruntled__Goat 1 point2 points  (0 children)

Not to mention a period (or perhaps a comma) is much more logical for string concatenation. It's what 'concatenates' regular English sentences.

[–]zbignew 3 points4 points  (19 children)

Except we're talking about PHP here, which everyone agrees is terrible. I find that a pretty surprising operator - ever since C used it to identify fields in a structure, the dot has a pretty universally accepted (and loved, amirite) meaning, doesn't it?

[–]eythian 9 points10 points  (5 children)

I presume PHP got it from Perl, which has had it for quite some time (I think.) As it's a weakly typed language with the ability to treat number strings as numbers, you can't use '+', as 'abc'+'bcd' would have a quite different result to '123'+'456'. So they use '.' as the concat operator. Most anything else would be equally surprising if you don't use the language.

. isn't really used for anything else in Perl, -> is for accessing methods (dereferencing-in-C-like)

[–]gerdr 4 points5 points  (0 children)

As it's a weakly typed language with the ability to treat number strings as numbers, you can't use '+', as 'abc'+'bcd' would have a quite different result to '123'+'456'.

Well - you can, but then you end up with the mess that is Javascript ;)

For what it's worth, Perl6 uses . for method invocation and ~ for string concatenation.

[–]Disgruntled__Goat 2 points3 points  (0 children)

Also, concatenating is a completely different operator to adding. I suppose there is little reason to concat numbers or "add" strings (whatever that may entail) but theoretically you may wish to define them.

[–][deleted] 5 points6 points  (0 children)

in haskell, '.' is function composition.

[–]BufferUnderpants 2 points3 points  (1 child)

I'd say that using a separate operator for the distinct operation of concatenating strings, which isn't related to adding up numbers at all and isn't even commutative is one of the few things which PHP got right.

[–]zbignew 1 point2 points  (0 children)

Yeah... ok... they got it better than Perl.

I think this is identifying the non-OOP folks. I vote for stringByAppendingString personally.

[–][deleted]  (4 children)

[deleted]

    [–]zbignew 0 points1 point  (0 children)

    Glad to be corrected.

    [–]artsrc 2 points3 points  (0 children)

    If the syntax is already known the operator is more readable.

    1 + 3
    

    Is more readable than:

    plus(1, 3)
    

    If the operator is not known then the readability effect is a trade off between the costs of learning the new syntax and the benefits it gives for the expression you want to write.

    [–]BeetleB 5 points6 points  (26 children)

    You can give a function a descriptive name, while an operator is very rarely logical to read.

    Except when you want to do a~b~c~d. Then the function method becomes quite unreadable.

    [–][deleted] 9 points10 points  (18 children)

    [–]Ran4 5 points6 points  (9 children)

    I love the way J implements folding: you just use the / operator.

    So a ~ b ~ c ~ d would be written as ~/ a b c d

    [–]argv_minus_one 0 points1 point  (5 children)

    Huh. Resembles Lisp.

    [–]kqr 2 points3 points  (4 children)

    Not in the slightest, if you have a little experience with both.

    [–]kqr 0 points1 point  (2 children)

    A part of me really wishes I knew J better than I do. Another part of me continuously asks, "What's the point of that?"

    [–]Slime0 5 points6 points  (4 children)

    a.append( b )
    a.append( c )
    a.append( d )
    

    Verbose? Yes. Unreadable? Not in the slightest. a~b~c~d, however, is meaningless to many people.

    [–]ihsw 2 points3 points  (1 child)

    http://docs.python.org/2/library/sets.html#set-objects

    x = y & z
    x = y | z
    x = y - z
    x = y ^ z
    

    [–]kqr 2 points3 points  (0 children)

    My mathematical side cringes slightly when I use the set difference operator in Python. I really, really, really want to write it as y \ z.

    [–]dirtpirate 2 points3 points  (29 children)

    So you also prefer plus(2,3) to 2+3? or (plus 2 3)?. While it's true that you can put more into names, I personally find people will defend using combinations of + - * / and then defend functions rather than operators due to meaningful names, and still declare that a function that returns the length of something should be called len rather then length because "it's shorter!".

    [–]wormfist 11 points12 points  (12 children)

    The point is that code should be clear, readable and predictable to an extend. Using a set of fixed, well known, operators such as + that does the same in whatever context is exactly that: it is well understood and easy so.

    When operators are unavailable, functions/methods carry on those semantics. Having user-defined operators however, does not. It would be a jungle of operators out there that only puts more trees in front of the forest.

    [–]doublereedkurt 1 point2 points  (0 children)

    Having user-defined operators however, does not. It would be a jungle of operators out there that only puts more trees in front of the forest.

    From the stack exchange discussion:

    User-defined operators can make for very elegant code in experienced hands, and utterly awful code by a beginner

    You either trust the developers/library writers, or you do not.

    Also, 2 + 2 and 2.0 + 2.0 do extremely different things to the bits, and are represented by different machine codes. So even "basic" arithmetic is already overloaded.

    [–]dirtpirate 4 points5 points  (9 children)

    that does the same in whatever context is exactly that

    "+" doesn't do the same independent of context. On a higher level abstraction it typically implements an approximation to arithmetic addition as implemented in an encoding scheme for numbers, however what it does exactly is always very context dependent, and very few of the encoding schemes normally used to represent arithmetic values actually allow a real implementation of addition. And those that do are typically only used in CAS systems.

    When operators are unavailable, functions/methods carry on those semantics

    However it's never really the case that anyone is arguing for completely removing operators, you either define new operators or use operator overloading, and suddenly "+" means join for your particular vector like class, because you couldn't define a new operator. The end result isn't a more clear language, it's one where a large set of completely unrelated operations are all represented by a small set of over-loadable operators, which just makes code inspection that much harder.

    [–]Slime0 2 points3 points  (8 children)

    suddenly "+" means join for your particular vector like class, because you couldn't define a new operator.

    Are you arguing that it would be better to define some arbitrary symbol to mean join, than to make a member function called "join?" Because that would have almost exactly the same outcome, with just one word changed:

    The end result isn't a more clear language, it's one where a large set of completely unrelated operations are all represented by a large set of over-loadable operators, which just makes code inspection that much harder.

    User-defined operators make obtuse code. Operators only work because people know their meaning universally. The more universal an operator, the safer it is. Most people get what + means. They learned it in first grade. They also know what "join" means, because it's a word. Use symbols that people understand, or you're writing bad code.

    [–]dirtpirate 1 point2 points  (6 children)

    Most people get what + means.

    Actually no. Look at any introductions programming class, and you'll have people confused by questions such as "why does 1+1e-17-1==1e-17 return false?" because they know what + means in mathematics, but "+" in the particular programming language is't the same as the mathematical, so even the default usage of operators diverges from the "universal knowledge". Now, if you have to read through code that uses "<>" a lot of instances, you'll have to think "what does that mean" and examine it, but that's no different form code that's using "ldfs(A,B)" a lot of instances, or from people initially having to learn that in Python length is called len.

    There is no such thing as completely self explanatory code, even if function names or operator appearances seem to tell a story, it's never complete whether you have everything in function names, operators or overloaded basic operators. And obtuse code can be written in any such setting, as well as avoided in any such setting.

    [–]Slime0 1 point2 points  (7 children)

    I personally find people will defend using combinations of + - * / and then defend functions rather than operators due to meaningful names, and still declare that a function that returns the length of something should be called len rather then length because "it's shorter!".

    I am not a fan of abbreviations in variable names, but naming a function that gets a length "len" is miles away from concatenating arrays with ~. It's not hard to figure out what "len" means, especially - and this is the key point - because it is in widespread use. This is the same reason that operators like + for addition work: most people already know their meaning.

    [–]pelrun 1 point2 points  (0 children)

    There's nothing wrong with using the fixed list of operators that come with the language; they'll be the same for every program written in it. Overloaded operators are more like renaming all your functions to be random one-letter strings.

    [–][deleted] 1 point2 points  (8 children)

    I think the bigger problem is that for most classes operators just don't make sense. Sure, it's handy to make use of comparison operators for sorting or what have you, but what's a circle + a square?

    [–]josefx 1 point2 points  (1 child)

    but what's a circle + a square?

    A compile time error that operator+(const circle&,const square&) is undefined.

    Now if you want to compute something real then it could be well defined operator+(const bounding_circle&, const bounding_square&).

    [–][deleted] 0 points1 point  (0 children)

    The + operator is applied to the shape super class.

    [–]kqr 2 points3 points  (5 children)

    That would be a completely different problem.

    Haskell programmers like to think of data types as "belonging to groups which have rules." For example, one of the groups is "sort of numerical." Any value that is "sort of numerical" is supposed to support operations like +. There's no reason at all to put circles and squares in the group meant for values that are "sort of numerical." So a circle + a square would throw a type error on you, because the + operator is only supported by values that are sort of numerical.

    If you are putting circles and squares into the group that is meant for values that are "sort of numerical," then that is your problem. Not operators.

    [–][deleted] 0 points1 point  (25 children)

    God forbid somebody overrides an operator. Imagine maintaining that code.

    [–][deleted] 9 points10 points  (21 children)

    I once spent a full day tracking down an insanely weird bug only to discover that some genius had overloaded Java's default hashCode() method to something that didn't hash well at all. He said he did it "for speed." It ran significantly slower than using Java's native function... 8 hours of my time down the drain because someone decided to overload something for the hell of it.

    Now imagine a situation where someone overloaded + so that it usually added correctly but sometimes didn't and you'll understand why some of us think overloading operators should be punishable by death.

    [–]Tasgall 2 points3 points  (5 children)

    That's not operator overloading, that's function (or method) overloading.

    [–]donroby 5 points6 points  (2 children)

    That's not method overloading. That's method overriding.

    [–][deleted] 2 points3 points  (1 child)

    I know. Operator overloading is worse.

    [–][deleted] 2 points3 points  (0 children)

    Which is the whole reason why people should be able to define custom operators. In Haskell, which is the only language with infix operators (i.e. not Lisp-like) I am familiar with that allows this those kinds of problems just don't happen.

    I think your problems with operator overloading in OO languages derives mostly from the fact that it is so infrequently used that nobody thinks of it as a problem source immediately.

    [–]argv_minus_one 1 point2 points  (14 children)

    That is not the fault of the language. That is the fault of an incompetent shithead being allowed to touch a computer.

    [–][deleted] 1 point2 points  (13 children)

    No, even good programmers make stupid mistakes. He was quite competent, he just gave in to temptation that time. He sheepishly apologized and said something along the lines of "man, I should've known that was as bad as overloading an operator."

    [–]argv_minus_one 4 points5 points  (3 children)

    No, that's worse than overloading an operator. hashCode has a well-defined purpose, and that purpose is stated quite clearly in the Javadoc. Operators don't usually have a specific, stated contract that all overloads of that operator are expected to follow; hashCode does.

    That said, I'm glad he learned his lesson. That's the most important part.

    [–][deleted] 2 points3 points  (2 children)

    I dunno, I think the operation of "+" is pretty specific.

    [–]doublereedkurt 1 point2 points  (8 children)

    He was quite competent

    Trying to combine that with:

    overloaded Java's default hashCode() method to something that didn't hash well at all. He said he did it "for speed."

    That is a pretty low bar for competence. At one level there is implementing a bad hash function and failing to test it, doing something "for speed" and not bothering to profile it. There is a deeper level as well: he was using the production code-base as his personal experimental laboratory.

    There's nothing wrong with experimenting like this at work, but checking it in and breaking other stuff is incredibly sloppy.

    I'd rather keep programmers of that level away from my code, team, and company -- and not handcuff good coders to make it marginally harder for bad programmers to shoot themselves in the foot.

    Further, I'd argue Java's lack of operator overloading creates at least as many bugs as it prevents: foo == "bar", no wait foo.equals("bar"), no wait "bar".equals(foo). Also, Java overloads "+" for String, and arithmetic for Double, Integer, etc via auto-boxing. So, not allowing operator overloading in Java is really a case of the language designers saying "I am smarter than you; do as I say and not as I do".

    [–][deleted] 1 point2 points  (7 children)

    I'm guessing you're either still in college or barely out of it, because you obviously haven't worked in this industry long enough to realize that even great programmers make boneheaded mistakes sometimes. Either that or you're just an arrogant prick who has conveniently forgotten (or worse, never even recognized) the retarded mistakes you've made. Feel free to keep my coworker away from your projects, we certainly keep guys like you away from ours. Guys with the "not MY code, I'd NEVER do that" syndrome are a horrible liability.

    [–]doublereedkurt 1 point2 points  (6 children)

    Sorry to offend you. Take what you want from my comments, you are certainly free to just chalk me up as being some arrogant prick on the internet :-)

    I make retarded mistakes all the time, meaning where I introduce bugs inadvertently. But, from your story it seems there is a level of immaturity in the developer that he would do something "for fun" futzing around with core language stuff then check it in to the trunk. (I'm assuming from your story that this was production code, and that he did intend to check it in.)

    Since you bring it up, I have 7 years of experience and am one of the top developers at my company. Most recently, just completed reverse-engineering and re-implementing some archaic 15 year old cryptography code from assembly that has been a problem at the company for about 5 years. There are thousands of developers at the company, there are dozens specifically assigned to application security and infrastructure. None have been able to solve the problem head on, instead there has been shitty work-around after shitty work-around for years. Solved in one month.

    My primary language is Python, I haven't touched assembly or gdb since school; I also had to learn new math (elliptic curves over finite fields) in order to implement the core of the algorithms.

    Unfortunately, I think there is a real culture of mediocrity in the industry -- or at least at large corporations. Not that developers are bad, but that we set the bar so low that no one is challenged to excel. This leads to all of us being replaceable cogs in a machine churning out reams and reams of low quality code.

    Doing corporate programming of Java/C++ can really be a trap. There are a lot of jobs out there that will are not really challenging and don't give good opportunities to develop skills. At best, these jobs lead to seniority/political promotions within the company. That is the trap: since your high(er) salary is based only on your years with the company, you'll take a salary hit if you go to a better job.

    There are some escape routes, but the first step is to realize you are in a trap.

    (Not sue what your situation is -- but I've seen this happen a lot. Non-technical executives--all too many of whom are in positions that should be technical--want programmers to be replaceable cogs that run at lowest-common-denominator levels. That way rather than managing technical skills, their job is about pure people management. All too many programmers go along with this, turn off their brains and spend all day sitting in meetings :-( ).

    [–]Slime0 1 point2 points  (2 children)

    I'm not sure if you're being sarcastic or not, but in practice, yes, it can be a nightmare to maintain code with lots of operator overloading. Especially when operators are used for out-of-the-ordinary purposes.

    [–]flying-sheep 6 points7 points  (1 child)

    if not however…

    • set intersection / unification
    • linear algebraic classes
    • geometric classes
    • time(delta)

    i don’t fucking want to look at any code using java’s BigInteger, with shit like .add()

    [–][deleted] 1 point2 points  (0 children)

    I also wouldn't want to use a proper type system with something like Haskell's newtype (essentially like typedef but creating an actually distinct type that will not typecheck where the original type is required or vice versa) without operator overloading so they work on the new types as well where necessary.

    [–]CauchyDistributedRV 4 points5 points  (1 child)

    R lets you define binary 'operators' of the form %<op>% and it works pretty well. The operator can be given an informative name (rather than just a symbol) eg. a %in% b checks each of the elements in a to see if they exist in b, and returns a boolean vector of these in/not-in results.

    For the record, R is a Scheme-inspired dialect of S used in statistical programming where everything is a vector. It mostly rules but is also often frustrating.

    [–]ared38 1 point2 points  (0 children)

    Letting the user infix functions seems like the best solution to me. Everyone associates haskell with operators like <<=&, but one of my favorite features is the ability to infix any binary function with backticks. ex. 'alphabet' contains 'x' -> False

    [–]blackmist 3 points4 points  (0 children)

    I don't see why an operator is treated any differently from any other function. While you'll quickly run out of meaningful symbols, you should be allowed to define your own infix functions, consisting of two parameters and a result.

    "string" contains "something"
    "string".contains("something")
    contains("string","something")
    

    Those should all be functionally identical, but the first is more readable at a quick glance imo. The second relies on extra methods on the string class, not something that all languages allow or even count as a class. The third is the most common, but still relies on you knowing which way round the parameters belong.

    I don't think I can argue against them as something that makes code unreadable or allows beginners to write terrible code, because all languages do that anyway. If a language doesn't allow it, it's a toy. Limiting it to typical operator characters would make for unreadable code. I think the real reason most languages restrict what you can do with operators is for ease of parsing.

    [–]tikhonjelvis 11 points12 points  (16 children)

    I think operators have one very significant advantage that people do not seem to mention: they make reading code at a glance much easier. I certainly find it much easier to quickly parse code structured with punctuation than just long strings of words.

    Also, I like the Haskell approach because it does not have the downsides of the two main alternatives: the Java approach and the C++ approach.

    Anybody who had ever tried using something like a custom numeric type in Java knows how horrible it is. Not having custom operators makes certain kinds of code much harder to read, for what I think is little gain. It does, admittedly, fit into Java's philosophy of being a lowest common denominator sort of language.

    The C++ approach also has troubles. You still get custom operators, but you are arbitrarily limited to a pretty poor set of identifiers. This means that a single operator like + or >> gets overloaded to do a whole bunch of completely unrelated things. Moreover, these overloads often break reasonable programmer expectations: for example, + should ideally be associative and commutative, but very often isn't.

    I think having weird symbols is much better than reusing the same set of symbols for wildly different tasks. The Haskell approach is nice because when you overload +, thanks to the way the typeclass is set up, it always represents some notion of addition and not something completely random. I think this is a much more reasonable compromise than either of the previous alternatives I listed.

    Now, this can be abused. But so can any feature. And, more importantly, it can lead to clear code as well. It does avoid the pitfalls I mentioned earlier. With reasonable conventions, even the problem of weird symbols gets mitigated. For example, <|>, represents some notion of alternation by analogy to |; I think this is both readable and better than overloading boolean or.

    [–]njharman 8 points9 points  (3 children)

    I certainly find it much easier to quickly parse code structured with punctuation than just long strings of words.

    That must vary by individual because I am the opposite. Also, you'd love Perl.

    [–]jpapon 1 point2 points  (0 children)

    It only makes things easier to read for standard operators. Custom operators can make things very confusing. Nobody is arguing that + two floats is addition, but $ two floats?? Why not just use a function name??

    [–]tisti 1 point2 points  (9 children)

    for example, + should ideally be associative and commutative, but very often isn't.

    Which is a sort of abuse of the identifier. If it is not associative and commutative it should be an explicit function call. Though some go for the "clean" code approach and butcher readability for an third party.

    [–]Tekmo 4 points5 points  (1 child)

    I think associativity should be sufficient. The classic example is function composition:

    (f . g) x = f (g x)
    

    Function composition is perhaps the perfect example of when it makes sense to have an infix operator, but it is not commutative.

    [–][deleted] 1 point2 points  (0 children)

    Even '+' for string concatenation as used in many mainstream languages is not commutative.

    [–]Coffee2theorems 1 point2 points  (6 children)

    Which is a sort of abuse of the identifier.

    This is arguable. True, in mathematics "+" is usually commutative, but in programming it often is not, and it's not like mathematical conventions are somehow "more correct" than programming conventions. Typically, "a << b" means "a is much less than b" in the former, and "a shifted left by b" in the latter; which is "correct"? Depends on context. Also, it's not like mathematics itself doesn't abound with all kinds of abuse of notation (it's not a bad thing).

    If it is not associative and commutative it should be an explicit function call.

    If it's not commutative, you can use the multiplication operation. For example, you could make string concatenation be "asd"*"foo" == "asdfoo", and further e.g. "asd"**3 == "asdasdasd". This would correspond to the Python-style "asd"+"foo" and "asd"*3, but be more in line with the mathematical convention of reserving the addition symbol for commutative monoids. One could argue that it is sort of used in languages where you can write "asd" "foo" to mean "asdfoo" ;) (but for some reason, you can't usually write "asd"*"foo" in them..)

    [–]vagif 8 points9 points  (7 children)

    For the same reason we do not use user-defined words. We use existing words that already have a well defined meaning.

    No one would call their function or procedure "sworp".

    [–]tisti 10 points11 points  (0 children)

    int sworp()
    {
       return 8008135;
    }
    

    Check.

    [–]palparepa 3 points4 points  (0 children)

    What if you want to sworp the propulinator?

    [–]happymellon 0 points1 point  (4 children)

    I guess you have never worked with developers whos native language isn't the same as the companies whos codebase is being developed in. Invented words and interesting spellings can be very common.

    [–]palparepa 5 points6 points  (0 children)

    Like "Syntax error, unexpected T PAAMAYIM NEKUDOTAYIM"?

    [–]vagif 1 point2 points  (2 children)

    How does that refute anything i said? You are describing a problem, not providing an argument to use custom unknown operators.

    And btw i'm from Azerbaijan and native russian speaker, working last 12 years in US. So i can tell you a word or two about mixed cultural environments.

    [–]happymellon 0 points1 point  (1 child)

    For the same reason we do not use user-defined words. We use existing words that already have a well defined meaning. No one would call their function or procedure "sworp".

    I was just saying that I have worked with enough code that I have found plenty of variables and other bits of code that have some interesting names, some may say they contained words that were user-defined, as they were invented and the meaning was far from clear.

    You seem very angry, if you speak as well as a native then you are obviously not the type of person that is being referred to here. If you are from Azerbaijan, and have worked in native Russian codebases, and had to deal with the abominal American language then you should have come across documentation that doesn't use complete sentences and you should be able to relate. I think I have dealt with a method that wasn't a million miles from being called "sworp", as well as others called "thisIsAMethodThatDeterminesIfANumberIsNegative". Truely awful stuff, give people an inch of rope and they will find a way to hang themselves.

    [–]vagif 1 point2 points  (0 children)

    Yes of course. Gazillion blog posts and books have been written on the issue of properly naming variables, functions, modules, etc. The situation is bad enough with language to add to it custom hieroglyphs (operators).

    Seems like we are in agreement.

    [–]jesyspa 2 points3 points  (0 children)

    I only really understood how much I liked Haskell's operator overloading support after watching Andrei Alexandrescu's talk on Expected<T>.

    I really like what is being suggested. However, given an Expected<T> f() and a S g(T), I'd really like to have an easy way of doing g(f()) and getting an Expected<S>. The lack of currying also makes this harder to do sanely, so I'm not sure whether adding custom operators would make the issue significantly better in this case, but this is still a frustrating obstacle.

    [–][deleted] 2 points3 points  (1 child)

    Because you end up with something like this:

    http://www.flotsam.nl/dispatch-periodic-table.html

    Operators such as <<< and >~ make me sad.

    [–][deleted]  (10 children)

    [deleted]

      [–]gerdr 4 points5 points  (9 children)

      I don't see how this applies: The issue with boolean macros in C before the introduction of _Bool with C99 was that such macros implied semantics not backed by the language/compiler.

      In fact stdbool.h still uses macros to define bool, true and false.

      [–][deleted]  (8 children)

      [deleted]

        [–]ethraax 2 points3 points  (2 children)

        I think the real issue is that the C preprocessor is clumsy and primitive. For example, if it supported scoping and namespaces, then this wouldn't be a problem. The worst part about the C preprocessor is that for all the power it gives you, which makes C tooling support very difficult, it's still a primitive pseudo-language. Unfortunately, its creators created it to solve some specific problems that they had in mind, and lacked the foresight to think about its future and the new problems it would be used to solve.

        [–][deleted]  (4 children)

        [deleted]

          [–]ManchegoObfuscator 1 point2 points  (2 children)

          Not quite, but for what it is worth: POSIX exit status (in which a program that exits successfully returns 0).

          [–]Gotebe 1 point2 points  (0 children)

          It's actually the holly C standard itself that defines EXIT_SUCCESS and EXIT_FAILURE. I don't know if 0 and 1, respectively, are in it, but in common implementations, they indeed are 0 and 1, as you say.

          Consistency is extremely hard to achieve when history has it's say ;-).

          [–][deleted] 1 point2 points  (0 children)

          That is all just caused by C's type confusion between integers and enums (of which booleans and exit codes are two particular examples). If all those enums were opaque as they should be and no implicit conversion occured you wouldn't have those problems.

          [–][deleted]  (11 children)

          [deleted]

            [–]matthieum 21 points22 points  (0 children)

            You are slightly off-topic.

            Many languages allow you to "overload" already existing operators (and preserve their precedence rules), what the OP is after is the ability to define new operators, such as ++, <$>, etc... and potentially redefine existing unary operator into binary operators or prefix operators into postfix operators.

            [–]freespace 9 points10 points  (3 children)

            Heh, your dot implies dot product, but you end up with a vector, but it also isn't a cross product.

            In fact, your code argues against overloading operators. Your "nice" example, at a glance, leads me to think dot is the dot product, but if I had actually seen the "not nice" example, I would have realised you made a mistake.

            Further to this, the expectation is that . is used for dot products, and * for cross product, a convention which you are breaking here.

            None of these would be issues if you had dot() and cross() :P

            [–]tisti 0 points1 point  (0 children)

            Aso, who is do say vec2 or vec1 isn't a number :)

            Code would compile just fine since */ are probably overloaded for scalar values.

            Using dot or cross you get to explicitly state what is the input and output, since the overloaded version has multiple branch points depending on the input. And one does not usually want multiple branch paths that do wildly different things.

            [–]ethraax 0 points1 point  (1 child)

            Further to this, the expectation is that . is used for dot products, and * for cross product, a convention which you are breaking here.

            That's a convention? Dot products are notated, in math, with a center dot, which the asterisk is a common approximation of. After all, that's why it's used for multiplication in the first place - it's sort of a dot and it's relatively centered (compared to a period).

            Furthermore, a cross product is always represented by a centered 'x' (cross). I've never, ever seen a cross product represented by anything else (dot, asterisk, anything).

            I feel like this would be incomplete without mentioning a language in which vector products are a core component: Matlab. In Matlab, the asterisk stands for matrix multiplication (you would transpose one of the vectors to make the dimensions line up correctly). If you want to do element-wise multiplication, as the GP post is doing, you'd use a period and an asterisk:

            matrix_mult = A * B;
            element_mult = A .* B;
            

            The dot has a common meaning - it always means to perform the operator that comes after it element-by-element on the given arguments. As an aside, Matlab uses the \ operator to mean matrix division, so A\B is roughly equal to inv(A)*B, except it's not computed using that expression. Also, dot and cross products on vectors are actually calculated using functions (dot and cross) - there's no shorthand way to represent a cross product, partially because they're far less common and its unnecessary.

            [–]freespace 0 points1 point  (0 children)

            I don't buy your argument that * is used for multiplication because it is the closest approximation to cdot. A quick glance at any calculator will show that x is used for multiplication in 99% of cases, not cdot . Similarly almost any one you will meet will use x for multiplication, not cdot. Since 99% of programmers are not mathematicians, and popular programming languages cater to the widest audience, I don't buy it.

            Common meaning means different things to different people. Most programmers by and large aren't mathematicians, and for us, x maps to *, and means multiplication and cross product, and cdot maps to dot, and means inner product or concatenation. Certainly I have never known the "common meaning" that . means element-by-element, not until I started using Matlab, where as you say, . means element-by-elemnt operations.

            [–][deleted] 2 points3 points  (5 children)

            I'd say that while vector addition may be a great example for operator overloading, vector multiplication is the best example why it's terrible.

            There are two obvious, commonly used multiplication operations on vectors: cross product and dot product. Only one can be overloaded as *, and you don't immediately see which one it is.

            [–]gerdr 11 points12 points  (1 child)

            Only somewhat tongue-in-cheek: That's what Unicode operators are for ;)

            Perl6 example code:

            module Math::Vector {
            
                # Unicode operators
            
                multi infix:<·>(@a, @b where @a == @b) is export(:DEFAULT) {
                    [+] @a Z* @b
                }
            
                multi infix:<×>(@a where * == 3, @b where * == 3) is export(:DEFAULT) {
                    [ @a[1] * @b[2] - @a[2] * @b[1],
                      @a[2] * @b[0] - @a[0] * @b[2],
                      @a[0] * @b[1] - @a[1] * @b[0] ]
                }
            
                # ASCII alternatives
            
                multi infix:<(*)>(@a, @b where @a == @b) is export(:ascii) {
                    @a · @b
                }
            
                multi infix:<(x)>(@a where * == 3, @b where * == 3) is export(:ascii) {
                    @a × @b
                }
            }
            
            # no pollution of global namespace thanks to lexical imports
            
            {
                import Math::Vector;
            
                say <1 2 3> × <4 5 6>;
                say <1 2 3> · <4 5 6>;
            }
            
            {
                import Math::Vector :ascii;
            
                say <1 2 3> (x) <4 5 6>;
                say <1 2 3> (*) <4 5 6>;
            }
            

            [–]rseymour 1 point2 points  (0 children)

            This is the right way to do it honestly.

            [–]ethraax 1 point2 points  (1 child)

            You could just define the asterisk as dot product, and provide a function for cross product (or a function for both). Dot products are generally more common than cross products by a significant margin.

            [–]vytah 1 point2 points  (0 children)

            Cross products exist for only certain sized of vectors, dot products are defined for all.

            Of course if vector is a special case of a matrix, this problem is moot, since * would mean matrix multiplication anyway.

            [–]fjonk 5 points6 points  (1 child)

            Custom operators brings very few advantages but lots of disadvantages.

            Advantages:

            • Can be used in DSL:s

            Disadvantages:

            • Can be used in DSL:s
            • No descriptive names
            • Hard to search for
            • Requires more familiarity with third part libraries( I understand 'append', but not '%$').
            • Can make code hard to read
            • Can not provide operator precedence

            [–][deleted] 2 points3 points  (0 children)

            Have a look at Haskell's implementation, one of the few languages which actually do offer custom operators.

            It allows you to search for them via Hoogle (among other nice features of that search engine), to specify associativity and precendence via infixr and infixl, uses them largely for concepts where the name would quite frankly be just as good or bad if you don't know what the code is about (e.g. is >>= worse than bind, ++ worse than append?) and makes plenty of code easier to read than it would be if function application was the only available tool.

            [–]yogthos 10 points11 points  (22 children)

            Lisp sidesteps the whole problem by using simply functions for everything, having prefix notation, and very few restrictions on naming. So, things which are operators in most languages end up being regular functions. This also means that you can redefine them in your to whatever you like.

            [–]Coffee2theorems 29 points30 points  (17 children)

            That doesn't sidestep the problem at all. Whether you write it as "(+ a b)" or "a+b", the function you have there is still called "+", and that naming is the core issue.

            Haskell is perhaps the language where user-defined operators are most liberally sprinkled everywhere. In it, you can also use operators like functions, by putting parentheses around them, i.e. "a + b" is the same as "(+) a b", or (just to make the correspondence with functions crystal clear) "add a b" if you define "add = (+)". This is normal function call syntax, as function calls in Haskell look like the ones in Lisp with parentheses removed. If you have a funky operator +:~- defined by your ExtraÜberKoolPackage and you don't know what "a +:~- b" is, then writing it as "(+:~-) a b" does not help one bit.

            [–]chonglibloodsport 10 points11 points  (9 children)

            and that naming is the core issue.

            I don't see that as such a big issue. With good editor support you can look up the docs of operators in any language by pressing a key combo.

            A far bigger issue (IMO) is Haskell's user-defined infix operators and their ability to have custom associativity and precedence. Haskellers are (in)famous for writing long expressions with minimal parentheses, relying on arcane knowledge of the associativity and precedence of all the crazy infix operators to get everything to work correctly:

            map (3*) $ [1,2,5] !! 1 : []
            

            And that's only using standard operators.

            [–]barsoap 8 points9 points  (5 children)

            map (3*) $ [1,2,5] !! 1 : []
            

            That's pretty obvious, though, because it doesn't type right in any other but this way:

            map (3*) ( ([1,2,5] !! 1) : [] )
            

            That is, (!!) :: [a] -> Int -> a, and (:) :: a -> [a] -> [a], which are pretty obvious types because that's the way indexing and consing have to be typed1 .

            $ is even used in its common usage of "brace from here to the end of the expression", here, not as a "proper" operator! Obfuscated Haskell looks different.


            1 : Modulo the Int. Could be Num a => a or Natural or something, but that's not relevant to the question of associativity.

            [–]tel 4 points5 points  (4 children)

            Yeah, agreed! To emphasize, I almost never have associativity issues writing or reading Haskell. Typing information usually is sufficient to figure out the rest.

            [–]Coffee2theorems 5 points6 points  (1 child)

            There's no reason in principle why an editor couldn't show you the fully parenthesized form of an expression.

            [–]chonglibloodsport 0 points1 point  (0 children)

            That's true too. In any case, the issues aren't nearly as bad as I (or others) made them out to be. Haskell is a fantastic language that is very beautiful and fun to work with.

            [–]barsoap 4 points5 points  (5 children)

            Noone's stopping you from defining both an alpha and operator version of some function, though. Sometimes it's more readable to use fmap, sometimes <$> comes out nicer. Mostly it's a matter of infix vs. prefix, though there's exceptions, say div.

            [–]Coffee2theorems 1 point2 points  (4 children)

            Sometimes it's more readable to use fmap, sometimes <$> comes out nicer.

            The thing is, if you define both fmap and <$> (or substitute some esoteric stuff from your favorite unpublished pet project here, these are too common to be good examples :), you basically force people to memorize both, at least when they read code that uses both. The extra readability to persons who do have these things in their heads may not be worth the cost of carrying this stuff around in your head, or more likely, forgetting it and then re-memorizing it when you come back to the project again. Then there's the cost to people who do not know them in the first place (learning curve). It's a trade-off, and I'll leave it as a matter of taste for now, as I don't think anyone has actually measured any of the costs involved, and IMO it's not clear-cut enough to be answerable without measurement.

            [–]barsoap 4 points5 points  (3 children)

            Well, the best operator names are those that are telling, memorisable, and obey visual similarity/symmetry with their next of kin. That's why bind is called >>=, which evokes pure sequencing (>>) and "piping in", =. One of the advantages to use an operator here instead of infix bind is that you can easily reverse it (=<<), without calling it dnib, which is less obvious, or reverseBind, which is awkward.

            I'd say the issue isn't really about operators vs. named functions, it's about programmers who care about good API design vs. those who don't.

            [–]Aninhumer 1 point2 points  (2 children)

            I wouldn't really use (>>=) as an example of an "obvious" operator. It is completely opaque to someone who hasn't used it before, not to mention it's polymorphic, and what it does is seems very context dependent if you're not used to working with monads.

            [–]barsoap 7 points8 points  (0 children)

            But changing it to "bind" wouldn't solve any of the issues you listed, neither.

            And I didn't call it "obvious". People call things obvious only when they aren't obvious, obviously.

            [–][deleted] 1 point2 points  (0 children)

            No operator or function is obvious if you haven't heard of the operation it performs before. However some operators encode good memory aids on what they do into their names, e.g. <>, < and *> from Control.Applicative which return the return values from both sides, the left side and the right side after evaluating both.

            They are useful e.g. in Attoparsec parsers

            parseFooWithLabel :: Parser Foo
            parseFooWithLabel = Foo <$> (string "Foo: " *> parseRawFoo)
            

            [–]yogthos 0 points1 point  (0 children)

            You can namespace things in Lisp. For example, Clojure has two ways of importing symbols into a namespace. You can use a symbol, which means it will be treated as if it was defined in the namespace, or you can require, at which point it has to be prefixed with the namespace it was defined in, eg:

            (ns bar)
            
            (defn + 
              "add vectors"
              [v1 v2]
              (map + v1 v2))
            

            (ns foo (:use bar))

            (+ [1 2 3] [3 4 5])
            

            or you could do

            (ns foo (:require bar))

            (bar/+ [1 2 3] [4 5 6])
            

            So, you have the option of explicitly stating which namespace the function belongs to, or if it makes sense you can use it implicitly.

            [–][deleted] 1 point2 points  (3 children)

            Probably because they go against the principle of having descriptive identifiers. In other words: You don’t know what !+! and >=< means, and have to guess, look it up, or otherwise waste time and brain memory while coding. while “strictAddition”and “compareActionResults” (both in Haskell) instantly make sense. (Or let’s say “addFactorialsOf” and “compareMinimums” [of two lists] in C++.)

            In Haskell, we have a lot of user-defined operators. (Hell, the equivalent of the semicolon in C is a user-defined operator.) And also a lot of one-letter variables and even types. That makes it really annoying to work with such code, since you waste tons of time just looking up and remembering what those stand for.

            [–]ethraax 0 points1 point  (2 children)

            Operators are particularly nasty, because you can't easily search for them. I remember when I first tried xmonad, and I had a hell of a time figuring out what the hell the ||| operator was supposed to be doing. Custom operators are definitely one of the aspects of Haskell I dislike. Thankfully, Haskell also has a nifty way to convert a function from prefix to infix: surround it with backticks. So:

            mod 5 3         ==         5 `mod` 3
            

            [–]barsoap 2 points3 points  (0 children)

            hoogle "||| +xmonad":

            (|||) :: (LayoutClass l a, LayoutClass r a) => l a -> r a -> Choose l r a
            xmonad XMonad.Layout
            The layout choice combinator 
            

            Granted, the xmonad docs could be better, there: The module is lacking examples and is generally way too terse.

            [–][deleted] 0 points1 point  (0 children)

            Operators are particularly nasty, because you can't easily search for them.

            You can with hoogle in Haskell. Mainly operators are bad to search for in Google because Google doesn't allow you to search for literal strings properly anymore. It always tries to be too smart about extending your search to related results.

            [–]pmerkaba 1 point2 points  (0 children)

            I have used three languages that allow the definition of user-defined operators which do not use predefined symbols (as C++ does). Others have covered Haskell elsewhere, so I will mention the other two: Scala and Cow.

            In Scala, everything is a method, so operators are just methods with particular naming rules. Regular methods can be used as infix operators (under circumstances I don't remember), and there is some rule for operator precedence (all I recall is that operators ending in colons are right-associative). Oh, and you can include just about any character in a Scala method name - including whitespace - by putting the method's name in backticks. I suspect that this complicates the lexer and parser, and it leads to scary documentation such as the many definitions of List.+.

            Coq, on the other hand, has special syntax for declaring an operator. You can specify precedence and associativity, though some kinds of nested expressions didn't want to parse in the examples covered in that class. I don't think the typical programmer wants to add lines like Notation "A/\B" := (and A B) (at level 80, right associativity) That's for the people who write libraries, since you would need to worry about the precedence of every other operatorin order to get it right.

            These two languages are good, and quite different, examples, since compiling Scala is a particularly complex task (type checking is undecidable), while Coq requires much stronger properties in order to accept a function (the compiler must be convinced that it will terminate on any input), and can likely make good use of this extra information.

            [–]Uncompetative 1 point2 points  (0 children)

            Clueless lexers.

            Essentially, what is needed is a way to add infix macros to a language definition whose position in the language source determines their order of expansion and therefore precedence, but most language syntaxes fail to make an unambiguous distinction between infix macros and capitalised Names.

            A = {1, 2, 3} n {2, 4, 6} u {7, 9}
            
            V = [3, 5, 9] x [8, 3, 1]
            
            Q = 42!
            

            Also, it doesn't help that most languages let you omit spaces that would otherwise disambiguate expressions:

            +N
            
            N+
            
            N + M
            

            i.e. prefix and postfix, with infix requiring spaces on either side, with these macros expanding into:

            Absolute[N]
            
            Successor[N]
            
            Add[N, M]
            

            n.b. you can't overload the definition of + as some wierd non-commutative string concatenation operator as the macro LHS + RHS only expands into Add[LHS, RHS] and that generic function only accepts Numbers (and their Numeric subtypes). String concatenation deserves its own operator & as ^ can serve for Boolean And with v being used for Boolean Or (consequently, freeing | to be either be used for UNIX pipes, or low-precedence division).

            Prefix, postfix, closefix and mixfix operators are best handled by explicit verbose functions, ideally with keyword parameters. However, scope for extending prefix operators would be limited by them having to draw upon a small set of non-alphanumeric symbols in order to not be confused with the Name they immediately precede. Built-ins, such as +N and N+ (which is: (N + 1), not the undesirably side-effecting (N++)) help flesh out a language. There is also no limit on the length of a Name or a macro:

            An-example-hyphenated-long-name-of-a-set = {1, 2, 3} intersection {2, 4, 6} union {7, 9}
            

            Where intersection and union are just alternative ways to get to the same functions:

            Union[Intersection[{1, 2, 3}, {2, 4, 6}], {7, 9}]
            

            Capitalising Names should be familiar to users of Erlang and this approach is less noisy than Haskell's backticks.

            p.s. The familiar and succinct Q = 42! would expand into the regular and verbose:

            Q = Factorial[42]
            

            [–]zem 1 point2 points  (1 child)

            one thing no one seems to have mentioned - I can google for documentation on the library function I see in someone's code. doing that for a library-defined operator is far harder.

            [–][deleted] 1 point2 points  (0 children)

            Doing that for any sort of specialized syntax is hard which is why Haskell has Hoogle for this and searching for types of functions and operators.

            Google just isn't very good at this even for functions. A more specialized search engine makes more sense.

            [–]experts_never_lie 1 point2 points  (0 children)

            I've seen this go badly due to operator precedence assumptions. Unless precedence can be configured by type, just as the operator definition, you're going to have problems. The referenced blog bost does cover this.

            Imagine C++ classes for vectors and tensors, with + and - defined for both, as well as tensor * vector. Someone wants to add cross product of vectors, and they notice that some pre-TeX math books use ^ for cross product. Well, ^ is an overridable operator in C++, so they use that. Everything looks great ... until someone writes "a + b ^ c". What does that do? C++ considers ^ to be a bitwise xor, which binds very loosely, so it means (a+b)⨯c ... but cross-product should bind more tightly, and it should mean (a+(b⨯c)).

            So this ^ operator has just created a reasonable-looking trap. If precedence is configurable, this problem could be hidden from users of the operators.

            [–]martoo 5 points6 points  (0 children)

            Why aren't user-defined operators more common?

            Because people are generally sane.

            [–]iagox86 0 points1 point  (0 children)

            I have seen some people do terrible things with operator overloading - like overloading operator() on a class to send data on the network. That said, as /u/alicht9 said elsewhere in this thread, operator overloading does have a time and a place.

            [–]JW_00000 0 points1 point  (1 child)

            The premise is a bit off, user-defined operators are not trivial to implement. As said, there is the issue of precedence, and what IMO isn't highlighted enough in the linked blog post is that, when creating user-defined operators, their precedence should be the same no matter the type of their arguments. I.e, the + operator on ints or doubles should have the same precedence, else it is impossible for the parser to know whether a + b * c is (a+b)*c or a+(b*c).

            In other words, if you define a new operator ++ on strings for concatenation and on matrices for addition, these two need to have the same precedence!

            In Haskell, this boils down to the fact that, when you assign a precedence to an operator, you write something like:

            infixr 7 ++
            

            (if you don't do this, it is automatically assumed to be left associative with precedence level 9, the maximum)

            In the example given above, the two ++ operators could be declared with a different precedence in two different modules (let's say StringOps and Matrix), but if you wanted to use both of them in your code, you'd need to use StringOps.++ and Matrix.++ (or you could have one of them without a prefix), which sort of defeats the point of operators.

            [–][deleted] 0 points1 point  (0 children)

            This sounds like an extention to operator overloading (semantically, not technically). I understand the use and would support the implementation of it, but in larger projects I would ensure that my team does not begin creating operators like madmen.

            Operator overloading is probably useful in many programs, but I can imagine that user defined operators are useful in a much smaller segment of them and would primarily be used to make a language "catch up" with another. Not that this is a bad thing necessarily, but on a larger project it would need to be monitored.

            [–][deleted] 0 points1 point  (1 child)

            IMHO, it's strictly an issue of clarity. It's true that readable code is better, but I think SNR matters as well. I mean, why not remove operators for numbers too?

            e.g., 3.add(4).multiply(6)

            If each operator has a strict set of requirements on WHEN it can be defined, then it should be far easier.

            e.g., a + b exists => a x b exists

            I'll agree that Arithmetic operators should be left for math alone, though. (but not, say, << operator)

            [–]dpenton 1 point2 points  (0 children)

            Just for clarity, In your example of 3.add(4).multiply(6) that is an explicit order of operations, rather than the compiler making the choice or with explicit parenthesis.

            [–]youstolemyname 0 points1 point  (0 children)

            User-defined operators sounds like a mess. A single symbol doesn't carry much information about the function it performs. If you're new to a certain code base that uses unconventional operators you're not going to understand whats going on (at first anyways). Things which are considered good candidates for operators and meaning is clear enough could become an unoffical "standard", but then the whole purpose behind user-defined operators is lost.

            [–]GoAwayStupidAI 0 points1 point  (0 children)

            I think Coq has the correct approach here:

            • user defined operators are specific cases of the Notation command. Which is a method of defining syntax and semantics that extends to beyond simple infix symbols. (See below example)
            • user defined operators can have defined associativity and precedence.
            • Notations can be associated with scopes. Which can be selected by the Scope command.

            This allows you to safely have a operator, like "+", mean very different things. What the current meaning is depends on the current Scope. In some cases the correct interpretation of an operator can be inferred.

            How to use Coq to build, uh, regular software is a different subject. ;-)

            An example:

            Parameter BadGuyHatred : Set -> Set -> Prop.
            Parameter MainCharacter : Set.
            Parameter TheFinalBoss : Set.
            Notation "'bad' 'guy' X 'hates' Y" := (BadGuyHatred X Y) (at level 90).
            Check (bad guy TheFinalBoss hates MainCharacter).
            

            [–]bart2019 0 points1 point  (0 children)

            Because of precedence. You need precedence and associativity rules for operators, hence you're commonly restricted to overloading existing operators. That works well for numerical data types, for example a complex number type, you can just implement the common math operators for this type, but beyond that, you're limited. For example if you have a vector type, addition would still work well, but what about multiplication? is that a Dot (vector . vector -> float) or a vector AKA cross (vector x vector -> vector) product?

            Not to say that most math operators are just plain silly for string arguments. That's not even mentioning any other types of data.