@infix to unlock operator notation is, in fact, controversial!

The SIP meeting notes (which, incidentally, are excellent!–I’m impressed by how many issues were discussed in that meeting, and appreciate how clear the write-up is!) from 2020-03-11 make this claim: "@infix is uncontroversial".

Please note this discussion, which was full of controversy: The @infix Annotation

In particular, the primary argument in favor of @infix is to increase regularity, but a case has been made that it will actually decrease regularity. There has been no clear argument that the original view is in fact correct!

It would be nice, if @infix is going in, to have at least an official acknowledgement of the arguments against it. Better would be to make counterarguments, and argue against alternative proposals that claim they would better accomplish the same stated goal. (The people who argued in favor of @infix did not, as far as I could tell, adequately address the criticisms.)

To summarize the long thread, the most weighty criticism to my eyes is the following:

@infix encourages regularity in how a single library is used, but discourages regularity in code that consumes libraries, as there is no mechanism to force different library authors to make the same decision about which methods are infix. If, for instance, one library makes contains infix and another doesn’t, you cannot adopt a consistent-with-the-library usage for contains. Therefore, the effect of @infix will not be, as it claims, to increase regularity, but actually to just decrease the usage of infix notation–it will always be bad practice to use something infix, even if it’s notated @infix, because you never know when some other library will fail to use the annotation, and your code will be internally inconsistent in style.

It would be nice to have a clear, reasonable comprehensive, and decently compelling argument in favor of @infix somewhere. Or, if deeper reflection suggests that the arguments against have sufficient merit, maybe the decision should be revisited.

It certainly shouldn’t just be classified as “oh, uncontroversial, so let’s do it”. If “uncontroversial” means only, “the people at this committee meeting all agree”, it’s kind of a shame that the people who believe this is a bad idea don’t have their position represented on the committee.

15 Likes

To give some feedback on this argument: I don’t think that will be a problem, at least not in the long run. There will be strong normative forces at work here. If a library deviates from the others in its choice of infix, but still defines common method names like map that are also defined elsewhere, users will not like the experience and stop using the library. In the end I believe the consensus will be predominantly dot syntax, which is fine by me.

There will be some exceptions, of course, e.g. by, to, until, eq, ne, min, max in the std lib. There will be others in DSLs and libraries, in particular to express infix operators.

1 Like

For alphanumeric method names, I see such a decrease, ecosystem-wide, as highly desirable.

1 Like

I am doubtful that there is such wide diversity of choice in libraries that people can stop using one simply because it messes up their infix syntax. With JSON parsers, okay, probably so.

But perhaps combined with peer pressure it will happen. Also, requiring @infix to match with inheritance provides further pressure to conform–otherwise you run into cases where it is impossible to implement two traits with the same method signature.

Maybe this will do the trick. Maybe it’ll be all over the place. I can’t predict, personally.

It would be nice if the documentation was upfront with this. It seems to be rather coy about that as an expected outcome.

The documentation says in justification, only

The purpose of the @infix annotation is to achieve consistency across a code base in how a method or type is applied. The idea is that the author of a method decides whether that method should be applied as an infix operator or in a regular application. Use sites then implement that decision consistently.

But you are not anticipating that this model (where the author decides) is the whole story, because the situation will strongly encourage the author to adopt the consensus. Someone else will already have decided for them, and they’d better go along or face the wrath of the community.

Furthermore, it’s also not accurate that “use sites then implement that decision consistently”. @infix does not allow the author to require infix notation! It only enables the option, which means that the author is free to decide whether they want the consistency of dot notation, or the inconsistency of whatever.

And, on top of that, the user has to remember whether it’s @infix or not. When it’s so much simpler to just assume “not”, why use infix except in vanishingly rare cases, even if the library author intended for you to?

The motivation section should be rewritten to give a realistic accounting of the actual achievable goals (or should admit that although this was the original motivation, it’s recognized that the goal will not be achieved as stated). The motivation is, or at least the consequence will be whether so motivated or not, to dramatically reduce the usage of operator notation, while providing authors the ability to retain it for the most critical operations.


I strongly disagree that reducing the ability to use operator notation is beneficial. As I’ve stated before, I think formatters and linters as part of the default toolchain are vastly superior, both because they can enforce consistency across libraries, and because they enable all sorts of other wonderful benefits for users. Furthermore, I’ve been programming for over thirty years now, with languages ranging from Modula-2 to Rust with a lot of other things in between, so I can without any hesitation say that losing operator notation is going to significantly (but not critically) negatively affect the readability of my code. I miss it all the time when I’m coding in Rust or C++.

But, hey, you win some, you lose some when it comes to language features. Not everyone feels the same way. I get that.

What I don’t understand is not using the actual motivations for motivation, and not advocating for the likely consequences (leaving people to discover it on their own).

I also don’t understand the characterization of the change as non-controversial, when there was quite a bit of negative feedback.

Both these things can be pretty easily fixed, however, if the resolve is to continue with @infix.

8 Likes

The intent of that wording was to characterize the proposal’s status among committee members whose opinion we know, not more widely than that.

I’ve submitted a pull request revising the wording in the minutes as follows:

-`@infix` is uncontroversial, ...
+No one in the committee who has registered an opinion is against `@infix`, ...

It’s a bit complicated because in the “straw poll” we did before the retreat, the @infix and @alpha proposals were treated together. We’ve now separated them from each other, but we don’t yet know how all of the committee members are feeling about them separately.

2 Likes

I think we have to agree to disagree here.

1 Like

@SethTisue - Great, thanks! I think it’s important to be clear about that, given that committee support isn’t necessarily reflective of community support.

FYI, I think @alpha is great.

Sure. Not all preferences are universal. However, it’s a lot easier to agree to disagree when (1) it’s acknowledged that there’s disagreement, and (2) the latest intent and best understanding of consequences are used to express the motivation.

This was not measuring up on either count, though it sounds like Seth is going to fix (1), so :+1: to that at least.

5 Likes

In Scala 3.4, I can’t do a zip b (e.g. (a zip b).map(f)) anymore without adding scalacOptions += "-Wconf:msg=is not declared infix:s" into build.sbt. That’s… sad.

(At least, there should be a shorthand a-la -language:infixOps. Or, at least, there should be a possibility to attach infix modifier to existing methods via extension.)

4 Likes

Tip: You can just write

(a `zip` b)

Makes it typograpically clearer what goes on anyway.

1 Like

Great thanks! (It appears that I was reading Rules for Operators very inattentively, sorry for that.)

(Still, to be honest, I still don’t get why some theoretically-usable-in-infix-style methods in standard library (e.g. a min b) should have a “privilege” over other theoretically-usable-in-infix-style methods in standard library (e.g. a zip b). Probably then I (if I were taking part in Scala standard library development) wouldn’t use infix modifier in the standard library at all, thus allowing only the a `op` b style and keeping a op b only for extraordinary cases in third-party libraries. That’s regarding to the message that by , to , until , eq , ne , min , max in the std lib would be kept infix.)

P.S.: BTW, I’ve just noticed that (in 3.4-RC1) a zip b gives warning only for tuples but not for seqs. (I assume, that’s because the warning is issued only when a method-defining library was also compiled with the new version of Scala.)

2 Likes

Yes, I agree. Probably adding more and more stdlib methods to be infix is a fool’s errand. I am sympathetic to the idea of requiring them all to be written in backquotes.

I would characterize “require backquotes everywhere” as “lack of sympathy”, even cruelty. (hyperbole alert)

I picked up dotted eq

(may I point out how uncomfortable it is to put that in backticks, typing in the dark at 5 am. My vi ESC pinkie gets confused about where to land. As a pianist, I ought to have an analogy about landing on a “black key” after a jump, but none springs to mind.)

x.eq(y) after seeing it a few times. I am not actually a fan of parenthetical contortions such as (x eq y) && cond; the neurons involved are the one that goes “eq or == here?” and the one replying “but we’ll have to add parens”.

My small contribution to the issues raised by Ichoran et al is in two parts:

It was mentioned somewhere that infix was previously conflated with alpha? It occurs to me that infix is natural for a name that is an alias of an operator name.

But what makes it natural is when the operands have the same type. The operator itself need not have any special properties.

xs zip ys is beautiful.

xs map f is weird, thought not as weird as xs map (_ + 1).

I’m not sure why the infix check is not a lint, with a “category” that simplifies the -Wconf expression to ignore it, -Wconf:cat=infix:s. That mechanism is currently underleveraged in Scala 3. As a lint, it becomes “opt-in”, though in practice the “community pressure” previously alluded to demands -Xlint always.

PS) worth noting the number of ideas or issues in the air at the end of 2019, then there was a pandemic (the lockdown here was March 2020), and now it is four years later. Folks who are new graduates were new students at the time.

2 Likes

Your dislike for infix is noted, but a bunch of the community, based on comments I’ve seen online, seems to disagree. I don’t have a real horse in this race (I don’t use it much myself), but I think the hard line against it is doing more harm than good.

Specifically, I think the fundamental problem with @infix – that it tries to regularize business-code style at the library level, which produces incoherent results – has been treated too lightly. A way to loosen the strings at the application level would be the appropriate improvement.

Conceptually, I suspect that the right solution is something equivalent to an extension method or implicit, that would allow a given application codebase to define a set of methods that will be treated as if they had @infix defined, with a single import to apply that – that would allow the application to be consistent and rigorous in its usage, without demanding that the entire universe be so. I have no clue whether that’s at all plausible, though.

3 Likes

or a config file combined with some sort of compiler flag?

3 Likes

I think the infix warnings is a reasonable improvement to the old Scala2 style of letting each callsite decide how it wants to call a method, for almost everybody:

  • For those who do not use infix methods much, it encourages a default style where each method has to be infix or not.

  • For those who do use additional methods infix, it forces them to list out those methods in their build file, and enforces that only the methods they intend to be infix can be used as infix methods. Most people have a subset of methods they expect to use as infix, and this forces you to be up front about it

Strategically, I think this is the right direction to go in. You get coarse grained flexibility at the definition-site or project level, rather than fine-grained flexibility per-callsite. This helps projects and the ecosystem as a whole become more uniform while still preserving the flexibility to use infix methods where necessary.

And the reason it’s a warning rather than an error is intentional: warnings are silenceable and meant to be silenced if necessary! So we should not feel bad about silencing warnings that are false positives, or do not apply to specific blocks, files, or projects. The warning is a gentle nudge in a particular direction that is generally broadly applicable, but one that the project owner can decide otherwise if they so choose

6 Likes

Could you please describe in more details what do you mean by that?

2 Likes

The unstated downside here is that you now have an extra bit of information to remember for every alphanumeric method name.

It’s unclear to me why the cost should be so high, or that it makes sense that this feature is best used by those with good memory.

4 Likes

Honestly, the introduction of @infix has been very surreal for me, given how little practical evidence I’ve seen (both presented here and personally) for there having been a problem with how people used the freedom to throw infix methods around at-will in Scala 2.

Particularly because, at least for me, far more jarring irregularities were introduced in Scala 3 that make me wonder who can honestly tell me that the problem with this code would be the infix method?

object Foo:
   def foo(si: Seq[Int]): Int = {
     si reduce (_ + _)
   }

Yeah, I’d have probably written it si.reduce(_ + _), but the inconsistent braces certainly seem like they’d be the bigger eyesore.

So what’s the actual problem here? Because if it’s regularity at the call site, that ship has sailed, and it might be worth considering if taking away infix methods is really worth all this fuss.

From where I’m sitting, it seems like a whole lot of effort to take away a language feature that a number of people really like, helps with writing DSLs, and doesn’t seem like it’s actually hurting anybody.

10 Likes

At the moment I use following string in my scalacOptions:

  "-Wconf:msg=Alphanumeric method .* is not declared infix:s",

This silences all warnings about infix methods.

Using this in SBT makes infix acceptable in my project anytime anyone sees fit.

As long as this work, I have no real problems with this feature, as I am not forced to use it. I would prefer the silencing to be less drastic (more similar to using other optional language features, like dynamics), but practically speaking this works fine.

(I hope this post will nor result in further tightening of ropes, which would really require me to list methods one by one or something like that).

2 Likes

I guess -Wconf:msg=Alphanumeric method .* is not declared infix:s is the new -language:implicitConversions.

It’s just silly that you can opt out of this feature with such a ridiculous incantation. This should be opt-int at best, or even better just left to a style checking tool like scalafmt.

Even sillier is the notion that xs zip ys is not ok, but xs `zip` ys is…

6 Likes