The @infix Annotation

I cannot speak for others, but for me, no. Consistency is the goal, if that can be achieved in any other way that would be fine. I just think that non-symbolic operators often don’t make code more readable and are the source of inconsistency.

Using default linters (or anything similar) from within the language might prevent that, but I guess the rules would ultimately also prohibit non-infix method names giving the same result as using an annotation. If linter rules could be set per codebase we don’t really win anything regarding consistency as a community.

Maybe if @alpha would generate the operator variant of a method then people can choose whether they like operators or methods, but each usage would be consistent.

1 Like

bmeesters

    May 28

I cannot speak for others, but for me, no. Consistency is the goal, if that can be achieved in any other way that would be fine. I just think that non-symbolic operators often don’t make code more readable and are the source of inconsistency.

That’s two different things. One is consistency, the other is whether non-symbolic operators are good or not. Coming from a background of Smalltalk, I strongly disagree on the latter, and I think my Scala code is the most readable and beautiful when I can avoid dot notation altogether. I also find that needing dot notation to do something is often a code smell.

Using default linters (or anything similar) from within the language might prevent that, but I guess the rules would ultimately also prohibit non-infix method names giving the same result as using an annotation. If linter rules could be set per codebase we don’t really win anything regarding consistency as a community.

Consistency as a community is very much what I’d rather NOT have. That’s the Python way, and it’s not something I appreciate at all. Consistency in a codebase according to the preferences of the people who actively work on that codebase, that’s what I like.

4 Likes

So presumably you would be happier if @infix didn’t exist so that different library authors couldn’t make different decisions about the same methods.

Would you, hypothetically, be nearly equally as happy with a rule that all methods with a single argument are to be written infix (symbolic or not)? This is also consistent with the syntax for match, so if adopted this would bring even greater consistency to the language.

If the community decided to adopt this, would you rather have a way to opt into dotted syntax in cases where you and/or library authors think infix is not so great, or would you rather that everyone use infix all the time?

1 Like

Yes, but as I said before (though half a sentence away, so maybe it got lost):

Perhaps not a very clear way to express the thought (sorry!), but nobody has carried along the argument against linters and formatters beyond

which has obvious solutions already in use by several programming language communities (gofmt, rustfmt).

Maybe I should be more precise: nobody has bothered to develop the argument beyond the point where trivial counterexamples apply.

2 Likes

Along these lines, the proposed change would greatly increase my cognitive burden while writing code: it’s an extra bit of information I need to remember on top of the method name, arity, and type. Theoretically there will be “tooling” or some kind to help with this, but sometimes I write Scala without it, and it’s not clear to me what the best tooling support would look like (does completing foo.infixM yield foo infixMethod? what about foo.infixM<cursor>(arg).dotMethod(aux)?)

There was passing mention of infix-only methods, and at the cost of wasted pixels I’d like to speak out even more strongly against them. Mixing infix and dot syntacies in the same expression is shockingly ugly: ((foo.bar(1) baz fiddle).zippy boffo ((qaz zap mix).go)).oof. Hopefully that’s not seriously on the table.

4 Likes

Also, re: the argument that this makes it easier on beginners: there’s a valid argument that not having infix syntax makes it easier on beginners, but having to explain "oh, get on Java Maps is called with map.get(key), but on Scala Maps it’s map get key" (to strawman a method which could conceivably be @infix) is not something I want to have to explain. Arguments like that might motivate removing mention of infix notation from tutorials.

1 Like

FWIW, this isn’t what you have to explain anyway: "but on Scala it’s map(key) which is actually map apply key, except you should really use map get key which returns an Option, which you could unwrap using map.get(key).get, except you really are better off using map or other higher-order methods on Option, so you really should map.get(key).map(f) and then you can fall back to a sane default with getOrElse, so you should map.get(key).map(f).getOrElse(x).

By the time you get through all that, I’m not sure the minor syntactic difference between map.get(key) and map get key is really that big a deal. Every bit of simplification helps, I suppose, but this isn’t where the main pain points are here.

2 Likes

Bah, I knew I had picked a bad example as soon as I posted it. My intended point was that explaining why two identically-named methods are called differently doesn’t seem much easier than explaining that you can call a method two ways.

2 Likes

This is wrong in many different ways:

  • Inconsistencies in beginners private code hurts the beginner; even reading your own code is harder if you haven’t been disciplined at maintaining conventions, and discipline and conventions is something you do not have as a beginner.

  • Inconsistencies in beginner’s private code ends up as inconsistencies in beginners shared code, giving Scala a reputation as a hard language for teams (which, almost tautologically, always start off full of beginners!) which end up with N beginners worth of different sets of inconsistencies

  • A “Team Leader” isn’t necessarily any more experienced in Scala as anyone else. Teams often have a broader purpose, not just being-good-at-Scala.

  • Needing to “add a build plugin / enable compiler option” is often an insurmountable problem for beginners.

  • Needing to “add a build plugin / enable compiler option” just to get to the baseline level of consistency that every language has by default, is the kind of thing that gives a language a reputation as convoluted and confusing.

3 Likes

None of these objections seem particularly substantive.

gofmt and rustfmt imply scalafmt for this case.

gofmt and rustfmt still imply scalafmt for this case.

A team leader can be expected to Google for getting started guides, which would prominently say, “use scalafmt”. Which presumably they would, if the tooling were in place.

So it should be on by default (and tell you so).

So it should be on by default (and tell you so).

(Also, Scala necessarily will never be quite as simple to tackle as those languages that don’t let you express very much to the compiler.)

So, again, I don’t see how all the objections aren’t better solved by tooling that you (could) get for free as part of the toolchain.

  • Beginners are guided into sensible default behavior
  • Experts can select the best notation to aid code comprehensibility
  • Beginners can make the expert stuff look like what they expect
  • Experts can (potentially) make the beginner stuff look (more) like they expect
  • Projects with contributors with different preferred styles have a relatively low-effort way to keep things consistent within the project

I can think of some actual downsides to the tooling approach, but maybe I can be excused from having the entire discussion about tooling with myself?

Also, given the radically different styles that Scala supports (full OO vs. zero OO full typeclass vs. mostly procedural), Scala is never going to be a language that has one narrow best way to do everything; that language wouldn’t be Scala. I have a great deal of difficulty imagining newbies that get over [] instead of <>, that figure out covariance and contravariance, that recognize how typeclasses supply functionality in generic contexts, navigate the differences between functions and methods, and yet get put off by being able to say "fish" charAt 2 instead of "fish".charAt(2) (but are okay with "fish"(2)).

It seems to me that we have the opportunity to smooth out a small burr encountered when picking up Scala by investing in tooling, and that we’d want to do this anyway in order to help Scala be more friendly in general; fixing this burr by removing a feature that a non-vanishingly-small fraction of users rely on to aid code clarity in difficult expert-level code seems–once it’s noticed that to be this way–a very poor alternative strategy.

Esteban Küber :crab: :gear: ‏ @ ekuber May 26
General reminder that if you encounter a rustc diagnostic error that confuses you for more than a minute, it is a bug. File tickets, we take them seriously. We want rustc to be your first tutor.

This is how you make things easier for beginners: you give them tools that help train them into experts.

5 Likes

I think both you and @Ichoran can agree that this “baseline” (dot notation) should just be default. I’ll disagree with @kavedaa that beginners should be left on their own.

An obvious compromise here is to add a -language:infixOps that experts can enable for their team if they know what they’re doing. There’s no reason this couldn’t be also paired with annotations from library authors for methods that really should be infixed regardless of settings (scalatest, sbt, or other DSLs that rely heavily on infix methods). In fact, there’s even room for a stronger version of @infix that forces infix in these DSLs if teams are allowed to increase flexibility for all the gray-area methods via compiler flag.

1 Like

As a long-time Scala developer, I have to say (even though this will be buried) that this proposal is really ill-conceived. If there’s regret around infix methods, own it and change the language. Whether infix methods are allowed needs to be a property of the language, not whether the library author thought about whether to put an annotation on the method. That is just going to lead to more confusion and ultimately kill infix methods, since it won’t be reliable.

If that’s the goal, then fine – kill infix methods in the language rather than by crippling them. Personally, I think that’s a really arbitrary choice. Clearly, it’s OK for the parser to deal with them, since it already does. Choosing to kill them to enforce someone’s idea of good style is not a good reason to change the language (and to be clear, I see the @infix annotation as tantamount to killing infix methods, since that’s what it will indirectly accomplish after a lot of confusion and damage to the language’s reputation).

Code style should be a function of the team that’s working on it. If I see (or someone on my team sees) questionable infix style in a pull request, we would point it out. Many teams also use linters or what have you. But if something is part of the language, it should be up to me (and my team) if it’s readable and clear.

Either way, if you want to dictate how the language is used, change the language! An annotation at the definition site of a function shouldn’t dictate the semantics of the language.

Personally, I tend towards dot notation in most cases, but I’d like to call out a couple of infix usages that I do use, and the fact that widely used DSLs also use infix notation for readability:

foo match {
  case foo if setOfThings contains foo => ???
  ...
} 

reads pretty nicely to me, and for contains in this situation I always want to use infix notation. If you’re a Scala maintainer and you disagree with me, own it and change the language.

ScalaTest uses infix notation basically everywhere. My tests shouldn’t stop compiling because I have some version of ScalaTest from before they added @infix everywhere.

Please change the language if you hate infix methods. Then we’ll all at least know which way the wind is blowing as far as respecting the users’ choice.

9 Likes

Another example:

for {
  i <- 0 until x
  j <- 0 until y
} yield (i, j)

reads a bit nicer

for {
  i <- 0.until(x)
  j <- 0.until(y)
} yield (i, j)
2 Likes

This isn’t quite a binary debate. There are enough variations of opinion flying around that it seems worth polling for what kind of solutions people find acceptable:

  • Disable altogether
  • Disable; libraries make exceptions via annotation
  • Disable; teams make exceptions via feature flag
  • Keep; integrate linting into scalac
  • Keep; encourage teams to use style checkers

0 voters

1 Like

I just wanted to point to the issue on dotty. It already has plenty of discussion worth checking out, including unimplemented ideas and guiding motivation that didn’t make it to the dotty docs linked above.

In fact @sjrd had a really good write-up in the github issue. I’d be interested in reading his response to the feedback in this thread.

1 Like

I just want to highlight something that I think is a big mistake made in all these discussions – which mainly involve people who are already pretty comfortable with Scala.

We hear things from beginners like, “There’s too many ways to do the same thing in Scala!”. And some folks take that to mean that beginners suffer a “choice paralysis” as mentioned in the article that @stewSquared linked:

am I supposed to use += or append in my code that uses the collections? I don’t know. Choice paralysis.
@sjrd

I think this is flawed reasoning. Beginners don’t wrestle with whether to use += or append, and since both are equivalent they suddenly decide that “there are too many ways to do the same thing in Scala!” and ragequit. Programmers are used to (and fine with) the fact that there can be named methods that do the same thing as operator overloads (to use C++ terminology).

What they mean is that there are too many ways from a design perspective, not from a syntax and method-naming perspective. Should I make an immutable functional API? If I do, then OOP people won’t know what to do with it. Or, I could make an OOP-style mutable API, but then all the FP-people will judge me. That’s what the “too many ways” question really comes down to. And it won’t be solved by choosing arbitrary syntax features and unilaterally bludgeoning them. In fact, having “too many ways” to do things in this sense is kind of what defines Scala.

Scala 3.0 is not an opportunity to silence all haters. I’ve heard somewhere about what haters gon’ do, and it’s not going to change. There are always going to be a lot of people who have some kind of complex about FP, or the JVM, or whatever else there is, and of that minority there will always be a further minority who make themselves feel better by picking out edge-case abuses of syntax and pretending they are the entire language. It’s not going to get better. Beginners are seriously fine already – there are lots of them and they do OK. We’ve hired several of them who are now quite formidable with Scala. I’m sick of hearing the argument of “we have to take away this feature because beginners are dumb!!” I have yet to hear from a single beginner who can’t get implicits after reading the docs, or typeclasses or even monads with a little bit of guidance. Let alone freaking spaces instead of dots.

Beginners aren’t dumb and they don’t need the language to be crippled. Every single person on this forum was at one point a Scala beginner – and you’ve all figured it out, haven’t you? Ask yourself if a contains b makes your brain hurt before projecting that presumption upon an entire class of people that you probably haven’t even asked.

I apologize for my rudeness, but lately the large majority of threads here are threatening to remove some power from Scala for the benefit of these mythical beginners, and I’m really starting to worry that if all these proposals succeed then there won’t be anything left.

7 Likes

Well true, but its the choice that gives rise to the inconsistency. So I want one or the other. I don’t think infix is bad for simple cases, but I also don’t think it does make much of a difference. So then I rather go for non-infix. I don’t see how it is a code smell rather than a difference of opinion on style.

On the last point I don’t think we can do anything else than agree to disagree. I don’t like Go, and am not fond of Python either. But I do envy the fact that their communities prefer to use one style. I think it makes it much easier to get started on an unknown code base, get more consistent teaching material over a whole community, and makes confusion less likely.

That said, it is just syntax, I don’t care that much. I have said what I wanted to say and understand if people disagree. Now its up to the SIP comité to decide how to proceed.

In principle, yes. I often don’t mind infix, and match is a good example of where it is used nicely. Also it removes some burden from the developer and enforces more consistency than @infix.

That said, if you refactor a method from one argument to two, you not only need to change all call-sites to use that argument, you also need to the style. How would that work with two parameters where one has a default? Would you change style based on whether or not you override the second parameter?There are likely too many edge cases for this to work.

So all in all, yes I would be open for alternatives, even if it includes more infix notation. But I don’t think we have a good alternative yet.

I just want to weigh in here: whatever you do, please think of users of IDEs. If I try to find a method on an object, I type a „.“ and get hints. If a method is only allowed to be an infix method, that seems not to work anymore.

1 Like

I use them for the creation of vectors and integer coordinates.

val v44 = 4 vv 4
val v31 = -2.4 vv 54.6 vv 34.7
val v32 = 4 vv -5 vv 0
val c1 = 5 cc -96

v44 ==> Vec2(4, 4)
v31 ==> Vec3(-2.4, 54.6, 34.7)
v32 ==> Vec3(4, -5, 0)
c1 ==> Cood(5, -96)

Using a short alpha name seemed better than making up some arbitrary symbolic operator, on types that will already have many symbolic operator methods in scope. I’m fairly easy going about whether we need an infix operator, but I would strongly object to their removal.

1 Like