Infix operator labeling

I am upgrading to 3.4, and it is causing some problems. I will probably have more posts about this, but for now I would just like to ask about infix operator labeling.

I got many warnings about infix operators that were not explicitly labeled as such, so I labeled them. I am just wondering what the point is of this explicit labeling requirement. Is there something risky about infix operators that labeling mitigates? Or is this labeling requirement a way of discouraging infix operators? If so, why?

When I wish to annoy people, I just propose an irksome -Xlint warning.

I’m not a fan of x compare y as a style, so I’m also curious whether there was an underlying motivation.

I see that an infix method still needs backticks for “leading infix” syntax.

The Scala 3 puzzler:

def f =
  ""
  eq
  ""

in case anyone is hiring and needs an interview question.

Edit: ok you hinted that eq is not infix here. Which eq is it? I remember they eta-expand in Scala 3. Not sure if the forum does spoiler alerts.

Rather than start a new topic, I’ll just piggyback on this one. As I said before, I was trying to upgrade to Scala 3.4. However, I am having some baffling problems, so I decided to revert back to 3.3.1 by using

scalaVersion := “3.3.1”

in my build.sbt file. However, now I am unable to even get my code to compile with 3.3.1. The only significant changes that I recall making were adding those infix labels. Should I be able to switch back to 3.3.1 and have my code still compile?

There is nothing risky about infix operators. Some people don’t like them. That’s about it.

If you consume Java code with operators like plus or add, your only workarounds are to either write them with backticks

time1 `plus` time2

which makes it harder to read as an operation (partly defeating the point of using operators as operators), or to use the magic compiler flag to elide warnings:

"-Wconf:msg=is not declared infix:s"

If you use scala-cli directives, it doesn’t like the spaces, but you can use periods instead.

I suppress warnings because there are thousands of such cases in my codebase, requiring me to fix many dozens of method signatures in order to only have hundreds of such cases in my codebase from Java.

In a release filled with nice features, this stands out to me as a pointless pain in the neck, but anyway, at least the warning suppression makes it fairly harmless and easy to ignore.

“You can never go home again.”

I have no strong preference for infix functions, but I use them just because they often read more naturally. I don’t even mind labeling them explicitly because that clarifies how they can be used. For my code it was only a few dozen cases, so I just bit the bullet and did it. But I have a much bigger problem now.

When I switched my build.sbt to scala version 3.4, I got around 30 errors and over a hundred warnings. I decided to fix the warnings first just because they seemed easier to deal with. Most of them were infix labels. I finally got the warnings all fixed, but then the errors completely baffled me.

After a bit of frustration, I decided to just revert back to version 3.3.1 and do a clean recompile. However, many of the same errors still came up – so now I am even more baffled. So I deleted my target directory and tried another clean recompile. Same result.

What is going on? Shouldn’t I be able to revert back to a version of the compiler that my code was working with? You guys are my tech support, and I am stuck. Any suggestions?

Is that clean as in sbt clean or really clean as in git clean -dfx?

Otherwise, one can imagine an incremental compiler bug allowing your old project to compile, while clean compilation would fail.

Is it helpful to ask what would be the first error? or a sample?

Edit: worth adding that backticks serve a few different purposes (patterns etc), so conceivably adding them can introduce changes that are not just syntax or a check of syntax.

I mean clean as in sbt clean. As for git, I don’t even use it on this machine. (When I get things working, I use rsync to move the code over to another remote machine, and I use git there to commit. That may be a bit unconventional, but that’s just the way I have been doing it.)

As for backticks, I don’t use them. I use the explicit infix labels instead.

For what it’s worth, here is my first error message:

[error] – [E008] Not Found Error: /home/rpaielli/trajspec/src/main/AirspacePars.scala:64:44
[error] 64 | case Urban => scalarSteps(1, 4, 0.5) * kft
[error] | ^^^^^^^^^^^^^^^^^^^^^^^^
[error] |value * is not a member of Vector[scalar_.Scalar].
[error] |Extension methods were tried, but the search failed with:
[error] |
[error] | method RoutePts1 needs result type because its right-hand side attempts implicit search

This is code that has worked fine for years. The error has something to do with an implicit conversion:

class Scalar1(val s: Scalar): // for "scalar * vector" multiplication
  def * (v: Vector[Scalar]): Vector[Scalar] = v * s
class Scalar2(val s: Scalar): // for "scalar * vector" multiplication
  def * (v: Vector[Int]): Vector[Scalar] = VectorI1(v) * s

There are two issues here. The first is why this code did not work for Scala 3.4.0, and the second is why it still does not work after I reverted back to 3.3.1. I probably did something really dumb.

I may be wrong here, but the compiler complains about Vector[_] not having the operator. Your definitions below are for the Scalar1 and Sclara2 classes.

If your kft is a Scalar1/2, use kft * scalarSteps(1, 4, 0.5) instead. Alternatively create extension methods for Vector[Scalar].

To test these cases you can also use the standard method call x.*(y) to make things more clear.

BTW shouldn’t Scalar1 and Scalar2 extend from a Scalar?

HTHs

EDIT: I suggest you upgrade to 3.3.3 first.

Thanks for the reply. As I said, this code has worked fine for years. It stopped working when I tried to upgrade to Scala 3.4, and I can’t get it to compile now even when I revert back to 3.3.1 and do a clean recompile. I think there must be some problem with sbt or the way I am using it. I am specifying the scala version by editing the line

scalaVersion := “3.3.1”

in my build.sbt file. Shouldn’t I be able to revert back to an earlier version that way?

As for the code shown, the Scalar1 and Scalar2 classes are just dummy names for implicit class conversions. The scalarSteps function just generates a list of Scalars, and kft is a Scalar that I use to represent units of 1000 feet (for aircraft altitude).

Not being able to rollback the language version rings a bell. The Scala installation got stuck for me once on what was then the latest language version. I can’t remember what I did other than trawl around the file system looking for bits of the Scala distribution and removing them, but I wonder if you need to delete either parts of the Coursier cache and / or the .sbt download in your home directory.

If you downgrade SBT you sometimes have to do the second, this may be the case here too.

1 Like

I renamed (disabled) my .sbt directory, deleted the target directory, and did a clean recompile. Same problem. It still seems to be using Scala version 3.4.0 even though I set it back to 3.3.1 in my build.sbt file.

(When you compile with sbt, it tells you which Scala version was used to build sbt but not which version it is using to compile the user’s source code – which I would think is more relevant to most users.)

So I made a copy of my entire source directory and did the same thing. Same problem still. And still baffled.

I did notice that when I go the executable path for sbt, I see a bash file that dates back to 2020. I’m not sure if I am even using that or not. Should I update that? If so, how? I am using Ubuntu Linux.

Next step - do a find in your file system for the scalac binary. Does it live in the Coursier cache?

My apologies. I though you were on 3.3.1 and had issues with compilation errors you did not have before. I have had this experience before even though I used Mill. It may be an issue with the Zinc compiler that both Sbt and Mill use. In my case I had to clean all output files manually and rebuild.

Instead of trying to track the issue, an alternate option is to make sure your source compiles ok on a clean install. How about setting up a VM or Docker image with a clean Sbt install and trying to build your code again?

Similarly puzzling but now “works”:

def f =
   "".
   eq:
     ""

I think you might do well by opening a Pre-SIP at contributors on how to address this in future Scala versions, now that there are some stories from the trenches…

But there’s no surprise. It’s going exactly as predicted (by people who use infix, and therefore knew in advance it was going to be a pain in the neck).

1 Like

Scala shouldn’t be a pain in the neck. :slight_smile: If you have the urge then you are, as anyone, welcome to start a Pre-SIP at contributors. I think we should be pragmatic if things can be improved.

It should for things that ought to be possible but discouraged. asInstanceOf is, rightfully, a pain in the neck to use.

The original discussions were pretty clear. Nothing’s really changed since The @infix Annotation - #56 by hrhino - Language Design - Scala Contributors and @infix to unlock operator notation is, in fact, controversial! - #19 by OndrejSpanel - Language Design - Scala Contributors.

The only thing that’s new is that Martin himself has been seen using infix notation, but with backticks, presumably because infix notation was clearer in that case (I agree–it really was!) but the method wasn’t annotated infix.

I’m content to accept that this ship has sailed and that the decision was made, whether for good reasons or bad, to make infix notation more of a pain to use. As long as it stays a warning, and -Wconf:msg= works, my personal pain is limited, and if anyone else finds it too onerous to switch to no-infix style, they can easily be told how to opt out. Using warnings a as a kind of clunky linting tool wouldn’t have been my first choice, but at least I can turn the “lint” off.

If anyone else wants to write a pre-SIP to rehash the issue, I’d happily chime in.

2 Likes

I got sidetracked for a day or two, but I still haven’t solved this problem. As some of you may recall, I tried to upgrade to Scala 3.4, but I had so many issues that I decided to revert back to 3.3.1. But I still can’t get it to compile, and I’m getting all kinds of weird errors.

I am using sbt and just specifying the Scala version in my build.sbt file. (I am not using Coursier.) I disabled my .sbt directory and upgraded to sbt 1.9.9. However, the version of sbt in my bash executable search path leads to something dating back to 2020, and I am not sure what to do about that or if it matters. I deleted the old scala versions hanging around on my system. I also deleted my target directory and did a clean recompile.

Here is an example of some of the strange errors I am getting. It’s telling me that

value head is not a member of Array[String]

How can that be? I even tried it in the REPL, and head works fine for Array. Any ideas about what the heck is going on?

Here is the full error message:

info] welcome to sbt 1.9.9 (Private Build Java 17.0.7)
[info] loading project definition from /home/rpaielli/trajspec/project
[info] loading settings for project trajspec from build.sbt …
[info] set current project to trajspec (in build file:/home/rpaielli/trajspec/)
[info] compiling 83 Scala sources and 1 Java source to /home/rpaielli/trajspec/target/classes …
[error] – [E008] Not Found Error: /home/rpaielli/trajspec/src/main/FlightInfo.scala:18:52
[error] 18 | val airportCode = AirportCode(txt.trim.split(“/”).head)
[error] | ^^^^^^^^^^^^^^^^^^^^^^^^
[error] |value head is not a member of Array[String].
[error] |Extension methods were tried, but the search failed with:
[error] |