The @infix Annotation

alpha is great. Seems almost everyone likes that.

I don’t like infix. I value simplicity, but the fact that the declaration site decides and can still decide to allow infix seems confusing to me.

I’d personally rather deprecate non-operator infix calls or restrict them as John suggested to methods like (A, A) => A.

3 Likes

I quote the documentation about @alpha:

The @alpha annotation has no bearing on Scala usages. Any application of that method in Scala has to use ++= , not append .

@alpha only serves as documentation and Java interop, even though many libraries—using e.g. macros, implicit derivation etc. already makes Java interop virtually impossible—and even more applications don’t care at all about being called from Java.

1 Like

Actually all of your points about making the language simpler (for beginners or anyone) are orthogonal to the issue. The proposed @infix mechanism will not make the language simpler - just the opposite. There will be more keywords (or annotations, what’s the difference to beginners), more rules, more complexity.

2 Likes

@alpha only serves as documentation and Java interop, even though many libraries

Serves me right for not reading closely. :smile:

In PureScript, names for all operators are mandatory, which I think has worked out great (the story, like you say, is less about interop and more about ergonomics).

2 Likes

Honestly, this sounds like a missed opportunity

5 Likes

Haha, I neglected to notice who wrote it, because I generally think such things are irrelevant :slight_smile:

I maintain, however, that saying that there are nine ways to define a function (pointing to that question) is still not an adequately accurate way to characterize it.

Well, that’s no good. Can’t we get it to be substantive always?

So when writing set theory, you don’t write a ∈ A, nor when evaluating integrals do you write _f(x) | x₀ (with a subscript x₀; we don’t seem to have the math-mode plugin installed here)? Do you not consider domain mapping A → B to be an “operator” (it’s certainly symbolic!), or element mapping f(x)y to be an operator?

Granted, operators within some algebra are more common, but it’s not like there’s no precedent for anything else.

Nobody has bothered arguing against linters and formatters yet. The suggestion was made several times.

I’m inferring that people who like @infix actually don’t feel that consistency is terribly important, just less infixedness; otherwise they’d have said something once it was pointed out that it doesn’t require obscure extra tooling (c.f. Rust and Go with formatters built into the default toolchain).

1 Like

@lihaoyi actually did address linters a couple times. The basic argument is that newbies wouldn’t know they’re supposed to use linter/formatter X for infix syntax.

An alternative that avoids this problem is expecting dot syntax by default, then using a compiler flag or language feature import to enable infix methods. Style checkers can come in after to ensure that certain method names are used consistently, regardless of what library they come from.

1 Like

If the goal is consistency, they don’t need to know that. Nobody cares (or should care) whatever inconsistencies a beginner (or anyone) have in their own private code. But if they’re on a team, the team leader could simply tell them to add build plugin / enable compiler option “style-checker”, and voila, problem solved.

1 Like

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