Are the magical rules about `:` operators required any more?


#1

I find the rules about operators with leading : confusing each and every time I hit them. They make various rules more complicated than required. Now that we can declare extension methods, so in effect declare infix operators, is this even required any more?

def (e: E)[E] +: (l: List[E]): List[E] = ... 
def (l: List[E])[E] :+ (e: E): List[E] = ...
def (lhs: List[E])[E] ::: (rhs: List[E]) = ...

#2

Not sure I grok how the points are related. I mean, the : indicates how the operator is applied – the fact that we can declare it with extension syntax doesn’t seem to change that :+ (append) and +: (prepend) are very different operations…


#3

The point of :* is to allow dispatch on the RHS rather than the left.

If the type of the RHS is statically known, however, there’s little advantage of dispatching on the RHS instead of defining a method on the LHS that takes the RHS as an argument. So the points are totally related–if you can define a method that works well on (LHS, RHS) instead of having to put it on RHS, then the syntactic benefit goes away.

The dynamic dispatch isn’t preserved, but you can always recover it again by delegating, e.g. using fake notation:

def[E] (e: E) :: (es: List[E]) = es.prepend(e)

So it’s a reasonable suggestion.


#4

So what @Ichoran said - now that we have extension methods, there’s no need for some magical rules about having some things that are applied backwards. You just define the extension method with the values on the side you actually want them.


#5

We’ve always had the ability to define extension methods, the only thing that’s new is syntax.


#6

(btw, it’s operators with trailing : that are special, not leading)

The main thing about : operators is that they are right-associative, rather than left-associative like the others. This is why

1 :: 2 :: Nil

parses as

1 :: (2 :: Nil)

If you drop the special rules of : operators, the above will parse as

(1 :: 2) :: Nil

which will do the wrong thing however you declared :: in the first place, extension method or not.

So nope, the proposal here is not viable.


#7

I think what the OP is referring to is this: http://dotty.epfl.ch/docs/reference/other-new-features/extension-methods.html#operators

The syntax provides for associativity by placing the parameter list on either the left or right of the method identifier. That’s the new syntax.

Edit: And @sjrd’s point above, in case it’s not clear, is that the associativity happens at the parser level at the call site, so it can’t be defined arbitrarily (even if the syntax to define it could exist, like it could given the new dotty stuff). It has to be unambiguous during parse, or it would be really hard to resolve methods (i.e. what if the RHS and LHS both had a method with the same name, but it’s left-associative on the RHS and right-associative on the LHS?)


#8

The main thing about : operators is that they are right-associative , rather than left-associative like the others.

See, that I’ve been coding in scala for half a decade and I still made that mistake should be telling us something. Having a colon does two things - i makes it right-associative, and it swaps the order in which you write the parameters down at the call site. Extension methods get rid of the need for the latter. Not the former.

Now that you’ve shown it with list consing, I see what you are getting at. I can also immediately see work-arounds, although none are elegant.

This all stinks of a language design driven by the special case of providing a syntax for cons lists. Perhaps I’m wrong, but do we actually use right-associative operations anywhere else? Is there some better way to specify associativity?


#9

Well, I still use the /: operator heavily and quite like it, although I gather I’m unusual in that…


#10

As somebody who writes Scala code in my spare time (for 5+ years), I don’t immediately know what the “/:” operator does :wink: Probably a fold or something. I would google for it, except that searching for programming language symbol operators is also somewhat tricky to do.

Although I’m generally a fan of the scala collections, do at times find myself struggling to find the right combination of symbols to say add something to a sequence, or list, or buffer, etc. Is it :+=, +:=, +=:, ++= , ::+=, or :::=, etc. To me, it feels like there is a bit too much magic.

So, possibly it would make the language more friendly if the only two special cases were “::” and “:::”, and perhaps if these were exclusively reserved for list data structures.

Rob


#11

Have you tried searching for symbols on Google lately?

The top hit on Google for /: scala is a tutorial on foldLeft.

2.13 is cutting back a little on the symbolic operators–every method now has a text equivalent–but I think the rules are mostly pretty simple. Aside from the weird list :: and :::, everything else is pretty straightforward, especially when you don’t use the mutate-in-place operators which are generally discouraged anyway.

But the rules are

  1. If it updates in place, it ends with =
  2. If the thing on the right is “in charge” in some sense (e.g. is the collection), then it ends with : (it is also in place, this “on the right” rule wins)
  3. If you want to combine with another collection , double the operator

That gets you 90% of the way there; Google (or SymbolHound) will handle the rest.


#12

Operators always have some kind of a mnemonic. Mnemonic that will help you with writing, but will unfortunately be lost on a new person reading the code. One of the best ways to deal with operators I’ve seen is in PureScript - there you can define symbolic operators ONLY as aliases for functions with alphanumeric names, so anyone reading can always lookup a name for an operator, or have the IDE rewrite symbolic code to alphanumerics on-screen.


#13

AFAIR special treatment of operators ending in : is not only used in lists, but in every order preserving collection, like: List, Seq, Vector, Stream, LazyList, ListMap, ListSet, ArrayBuffer, etc


#14

Re: Google: Yes I tried it as I was writing the reply, and didn’t have any success. I didn’t use the same incantation as you, so instead I resorted to pulling up the documentation for Seq and looking at the operators.

Knowing the rules is fine, but that doesn’t help a casual reader understand your code.

I actually agree with kaishh that the symbol operators should just be aliases for named functions. Then a decent editor can always convert between the two.

I really like Scala as a language, but when using it, I feel that it has a fair amount of what I would call “accidental complexity”. I’m hoping that Scala 3 ends up being an easier language.