Which stdlib functions and types should be marked as `infix`?

Incidentally, is anyone actually enjoying infix? Does it make people’s code better?

It’s partly in the nature of the feature that only the pain points tend to get attention, but still, approximately every comment I’ve seen since the feature has hit has been someone inconvenienced in some way. For instance, the Scala 2 libs might get tasty annotations, but the Java standard libs won’t, Apache Commons Math won’t, ImgLib2 won’t, etc…

Is there any way we can know semi-objectively whether it’s making the Scala experience better (and for whom, if the experience differs between people)?

1 Like

Would it be possible to make a function infix on import, instead of where the method is defined? That might make DSL design - where infix really shines - more natural while eliminating the dissonance when the infix is used sparingly.

I think if it wasn’t bundled with all the Scala 3 features, it would have never gone passed the SIP committee. I’m a heavy DSL user and failing to see any benefit. Only increases technical dept.

5 Likes

As I understand it, allowing it to be set on import would undermine the justification for the feature - to let library maintainers dictate if a method could be called infix or not.

I don’t agree with the premise that infix is going to make code more regular, but that’s my understanding of the reason for it’s existence.

2 Likes

I think the answer is: Basically no user code should be infix, except if it’s part of a particular fluid DSL. That’s the only way to enforce syntactic uniformity.

Outside of DSLs, the only exception I see is operators that are known throughout the system such as eq, ne, min, or max. But even for these it’s debatable.

Note that you can always write an operator between backquotes. E.g.

   myList `contains` myElement

That’s a bit more to type, but much clearer.

1 Like

I am afraid I do not want to be enforced this decision in my code - and I think I am not alone.

I find 0 until 10 very nice, 0.until(10) outright ugly and 0 `until` 10 unnatural. When the operation acts like an operator, only there is no suitable symbol for it, I really prefer to use infix syntax - like dot or union.

5 Likes

I really dislike the backticks and find them to be even more distracting then the braces { } and ( ) we tried so hard to eliminate (which was a big improvement btw). The solution with infix is just fine. Let’s keep it.

1 Like

Right. I guess until and to would have to be included in the list of language-wide quasi-operators. They are effectively syntax.

2 Likes

Right, but – so what?

I mean, I get syntactic uniformity as a pure ideal. But after 15 years in the trenches, programming Scala full-time on a variety of projects with a variety of coding styles, I honestly find it to be one of those ideals that is intellectually pretty but counter-productive.

Getting back to @Ichoran’s original point, it looks to me like the infix requirement has been a net negative: it enforces a uniformity that isn’t actually very helpful in practice, while eliminating a flexibility that is useful for helping create readable, maintainable code.

Yes, that flexibility needs to be used judiciously at the call sites. But that’s true of absolutely everything. I have a button somewhere that says, “There is not now, and never will be, a programming language in which it is the slightest bit difficult to write bad code” – I think that’s pretty much a truism. But in practice I haven’t often seen infix syntax misused in ways that are particularly confusing, and I often have seen it used in ways that enhance readability.

Overall, I think the infix restriction is looking to be a net negative in terms of code maintainability – and to me, maintainability outweighs everything else.

4 Likes

Hard disagree. The syntactic non-uniformity is one of the first reasons not to adopt Scala. I have learned that from interacting with many people outside the Scala community. In retrospect, allowing alphanumeric infix methods was a mistake. We would have been better off by defining a fixed list of words in the syntax that behave like operators. Now we have to live with it and are slowly migrating away from it. And there will be no turning back, as far as I am concerned.

That’s rather at odds with my experiences, teaching and evangelizing Scala for a variety of organizations.

But even if I were to grant that, Scala 3 made things far worse, by introducing the braceless style. I’ve never once heard anyone even blink at the infix notation, but people constantly talk about Scala 3 being a completely different and incompatible language because the new braceless idiom looks so wildly different from Scala 2 code, and in practice encourages less uniformity in coding style. (Since many of us find the new style hard to read, and use it sparingly.) I spend a lot of time convincing people that it’s even the same language, much less that it’s largely upward compatible.

So even if I agreed with the point (which I don’t – I have yet to hear a single person talk about syntactic inconsistency as a reason they are reluctant to use Scala), we aren’t by any stretch of the imagination seriously trying to improve that.

6 Likes

I’m personally in favor of limiting infix alphanumeric methods overall.

There are some valid use cases where the method is commutative or symmetric in some way, like eq or max, that I agree work fine infix. But the Scala community as a whole has gone way to far in the past.

Even things like 1 to 10 and 1 until 10 are weird to me. Range(1, 10) and Range.Inclusive(1, 10) are my preferred way of spelling those, and are IMO a lot clearer than trying to remember which of to and until are right-inclusive vs right-exclusive when the english meaning of the two words is exactly the same.

I know not everyone agrees with me. It’s subjective, like Ruby vs Python. And in this case I fall squarely in the “Python” camp of parenthesized method calls rather than Ruby’s whitespace-delimited method calls

If people agreed with me on this and other things, I wouldn’t have had to spend the last decade building my own Scala software ecosystem from top-to-bottom. And yet here I am, perpetually fighting the current in a sisyphean struggle

3 Likes

I think we all agree that infix methods can be abused
And that a language without any infix methods (or only symbolic ones) is simpler, but that they are currently part of Scala
Furthermore we can maybe agree that some infix methods make code easier to read, and understand, sometimes (But maybe not on which methods and which times)

And we seem to go around in circles arguing that “1. is more important than 3.”, that “2. is true”, or other such things

But these are red herrings, the question is only:
“Given the language as it stands, with it’s current libraries and features, is the infix keyword a worthwhile means to address these points ?”

We can simplify it further:

  1. People experienced in Scala are not abusing infix methods (Though they might have learned it the hard way)
  2. Infix non-symbolic methods have to stay in the language in some way, so all their technical complexities will remain (i.e. we cannot simplify the spec/compiler, only make them as distant as possible from users, see XML literals in Scala 2)

Therefore, the question becomes “Is the infix keyword a good way to make sure new Scala developers do not abuse infix methods ?”

And I believe it fails in that regard:

  1. Users will have one more thing to worry about “Can this method be called infix ?” (which is distinct from the question of “should”), through one more keyword and some extra rules (symbolic methods do no need infix)
  2. If they are not taught about infix methods at all, the keyword does not change the situation
  3. A keyword does not teach users why something is bad, it just makes it slightly harder to do
  4. We should teach users why infix is dangerous, but this is the case regardless of whether the infix keyword exists !
  5. New users write a lot of the code they use themselves (you don’t usually start learning with a library), it is therefore easy for them to add infix in front of any method they want. They’ll just be frustrated later when using libraries and all of a sudden the bad habit they had is no longer possible, they might learn then the lesson, or just open an issue/PR to add infix

In summary, I believe the infix keyword is a bad solution as it’s trying to fix imperfect documentation/learning material through an addition to the language (and would then require more documentation and learning material)

3 Likes

I’m not sure everyone would agree upon this.

Maybe some people see input.split("\\s+") to Vector or input endsWith "oops!" and recoil. Maybe others are like, hey, that’s a really clean and clear look–let’s use that! (There is no “learning the hard way” because it is clean and clear. They’re not wrong. But it might not be to everyone’s preference.)

Personally, I find that infix coheres very nicely with fewer braces (both are less pointless visual clutter), so I’m using it way more than I used to. But it could well be that my style would drive Li Haoyi absolutely batty.

Anyway, I am not sure that arguments going round and round even in better-focused form like you suggest are all that useful either. If we don’t have any way to get data about how painful it is, if it’s working, or if anyone likes the impact of the feature itself (rather than liking the goal of having less infix)

Well that just tells me you don’t use ranges. When you want to use something a lot, you call it P or T or somesuch. I think it’s a great insight that it’s fine to make the critical common stuff super-easy to use.

If one uses a lot of ranges, one knows what to and until mean, and one doesn’t want it longer. (It could be different, but not longer.)

2 Likes

This is where your argument falls down. New users will not add @infix in front of everything.

  • They will not be able to add infix in front of every standard library method,

  • nor will they be able to add infix in front of any third party libraries.

  • PRs to add infix to the standard library for random methods like Iterable#map will probably be argued over and rejected.

  • PRs to add infix to third party libraries will be met with confusion (“Why would you can to call .toFooBar(qux: Qux) infix???”). PRs to add infix to the Java standard library are impossible.

  • They will not know to add infix in front of the methods they define themselves. It’s not like there’s a wealth of blog posts and books talking about using that keyword

What will end up happening is that the new users will just not call any methods infix unless the library author explicitly wanted to be called infix, and would just not define any of their own methods that can be called infix until they become more advanced users and learn about the feature from more advanced sources

To me that is the intended outcome. Not no infix methods, but only infix methods that the definition author intended to be infix, where the definition author knows what they are doing

Well that just tells me you don’t use ranges. When you want to use something a lot, you call it P or T or somesuch. I think it’s a great insight that it’s fine to make the critical common stuff super-easy to use.

If one uses a lot of ranges, one knows what to and until mean, and one doesn’t want it longer. (It could be different, but not longer.)

If you’re using them a lot, it’s fine to add your own helper methods. I myself have several such helper libraries, including os-lib and requests-scala, that are simply wrappers around parts of the standard library that I use a lot and want to be shorter.

If such helper libs became popular, that would be convincing to me that is resonates broadly with the community. Otherwise, “one person does things that way and likes it” doesn’t really help make a judgement of a feature’s net pros/cons across the language community


Forcing infix is an explicit choice that restricts what people do. Obviously the people who enjoy the thing being restricted won’t like it. The bet is that there is a large pool of people who would appreciate the limits, or would appreciate the additional uniformity that the restrictions enforce.

It’s fundamentally a bet between Ruby-like or Python-like syntax. It’s a bet on Javascript v.s. Coffeescript. There are plenty of people who like Ruby (and liked Coffeescript! Remember when it was the default for Ruby on Rails???) but I think the market has spoken which one is more adoptable, and Scala is clearly trying to move in the Python-like direction for its surface syntax

2 Likes

Why would they know anything about what the library author wants?

They simply won’t call anything infix at all.

Knowing what a library author wants, knowing how to annotate your own stuff, and knowing how to turn off the warning are all of roughly comparable complexity.

Remembering everything every library owner wants is of much greater complexity–the solutions there are “never use infix” or “turn the warning off”. This goes for experts and new users alike.

4 Likes

Is it really so hard? We already need to know what the library author wants their method to be named, how many parameter lists the method should be called with, what the library method’s parameters typed are, what the parameter names are if you want to use named params. You have to know whether library authors want their types to be covariant, rather than specifying the variance of a generic at every use site. Newly in Scala 3, you also need to know how many empty parens lists a method is defined with to call it, and can’t just randomly leave them out anymore

In general, “you need to know how a library is meant to be used to use a library” is a non issue for me, and I think most engineers. Of course the person defining something specified how it is used! Of course the caller needs to know things about the method they are calling in order to call it! Are we also going to complain that needing to know a method name to call that method is a huge hardship?

Obviously the ruby folks think differently - they also allow method calls with and without parens - and some people here do too. But I don’t think anyone here is going to be convinced by any of this discussion. But I hope people can understand the problem space and solution space and discuss it in that context, rather than harping on and on about the minutiae of this specific feature

2 Likes

Add to that how in the future you will have to know which parameter can undergo implicit conversion, marked by into. So fully agree.

Yes, and good libraries choose good names to help as much as possible with this because it takes attention. length or size or len or count or dim or dims or…? Furthermore, autocomplete and “did you mean X” messages are all there to help you out with this task because it’s harder than we want it to be.

In any case, one has little choice but to name things since types are not enough most of the time to figure out what is going to happen. (e.g. (Int, Int) => Int is rather underspecified).

These are also hard, but the first actually helps with the second because you can use grouping to aid the task. But, anyway, IDEs tell you this stuff incessantly because it’s harder than we want it to be.

In any case, we can’t get away from knowing what order to put parameters and which parameters to put, because the set of parameters is often not enough to determine function. (E.g. (Int, Int) => Boolean might be antisymmetric.)

This is a pain point. a? x? element? Most people–especially newbies–do not use named parameters ubiquitously for exactly this reason. It’s harder than we want it to be.

This is mostly because it’s hard to code so that use-site variance is correct. This is a safety issue, primarily.

Yes, and that’s a pain point too. next or next()? hasNext()? And we can still elide them or not, as we please, for Java.

I think it’s an open question as to whether this was worth it. The exception for Java is especially questionable.

Again, I am not convinced that this is wise. Again it’s more to remember for dubious benefit. How do you know what I need to get into that argument slot?

Of course! And it’s a lot. It’s one of the things that makes programming hard. So don’t make it gratuitiously harder by adding extra superfluous stuff to remember! We put so much effort into making this easier:

  • Types help by catching you if you forget
  • IDEs help by offering you the most important info
  • Immutable by default means we don’t have to worry about which library author thinks it’s cool to mutate internals so we don’t even need to think about it

And so on.

4 Likes

(inb4 @alvae comes to remind us our vals are not as safe as they could be :stuck_out_tongue: )

More seriously, I agree with everything you said

1 Like