The @alpha notation

As far as I understand it, the @alpha notation is still being discussed if it should be mandatory or not for 3.1 or later, while it maybe will be deprecated but non-mandatory in 3.0; although, the operators dotty doc page currently says “An @alpha annotation will be mandatory if the method name is symbolic. Symbolic methods without @alpha annotations are deprecated.”

In my teaching of beginner programmers using Scala as first language, I have numerous simple examples, exercises and assignments including common symbolic operators with intuitive semantics, such as:

case class Time(ms: Int): 
  def +(other: Time) = Time(ms + other.ms)

IMHO I think it would really clutter life for beginners if they must dig into special imports and annotation syntax just to create simple code like above, and examples like above is a perfectly sensible thing to write.

I also think being beginner-friendly and concise in this case outweighs the potential benefits of forcing all symbolic methods having extra markers/imports.

So with this thread I’d like to discuss the proposal not to deprecate operators without @alpha, but instead make the @alpha notation an optional feature that comes with an auto-generated alpha-named method that can be used as an alternative for those library authors who want to offer that and then also automatically include that alternative method in the docs.

For reference: the @alpha notation was discussed but not decided in SIP minutes 2020-03-11 and mentioned in these threads here: @infix notation 1 @infix notation 2.

11 Likes

I completely concur. I feel like any benefits gained from this are outweighed by the complexities it would introduce, especially for people learning the language. Examples like 3D vectors and complex numbers can show the power of the Scala syntax and this would hurt such examples.

7 Likes

I was excited when the @alpha proposal first showed up, because I appreciated the prospect of having a lot more clarity about what operations symbolic operators are meant to perform.

These sorts of operations are often already provided with both symbolic and descriptive names. I was expecting the behavior of @alpha to provide something like this in a more principled way. For example, having:

@alpha("add") def +(that: T): T = ???

would provide both a method named add and a method named +, would unify both definitions in any scaladoc output, and would perhaps provide more feedback via completions in REPLs and integrated editors that would show the name add when using + and vice-versa.

So, I was taken aback when the behavior of @alpha turned out to be making only the symbolic version available in Scala, while the alpha version would be available to other JVM languages–and further, that it prevents the alpha name from being used in another definition, because the two methods are treated as having identical names and signatures.

I’d like to see this go back to the drawing board, but not for it to become dropped as an idea completely. Being able to easily see “oh, this operator is being used for the Hadamard product” would be fantastic.

Optimally, the implementation I would like to see is that a single definition causes both the symbolic and the alpha name of a definition to be available from Scala, and that not including an alpha name for your symbolic method names would produce a warning with recommended warnings turned on.

Optimally, documentation for both names would be unified and other tools would have enough information to make their common identity clear.

10 Likes

Thanks for the input! We discussed the @alpha annotation a while back in the SIP meetings. No formal vote was taken but the tendency echoed your sentiment: @alpha should remain optional for symbolic operators.

I’ll change the docs to remove the critical sentence and put the warning under a flag.

By contrast, it was a conscious, und unopposed, decision to make the alphanumeric name not callable from Scala. The motivation is that we want to avoid providing more than one way to do the same thing here. Library authors should decide whether their methods are alphanumeric or symbolic and clients should keep to that decision. @alpha is still useful for interop, and to avoid strange-looking symbol encodings in stacktraces.

There’s an open discussion whether we want to rename @alpha to something else. Maybe @hostName?

4 Likes

Say, I declare a type A in Scala with a method +, to which I add the alpha notation “plus”.

Now I declare in Java a type B which extends A, and Java only sees plus, so B only has a method plus. Can Scala now see that B has a plus method, or does it see + instead? After all, B is a Java type, so we should see its members in Scala just as they are in Java, right? What if I override the method in B?

What if then, in Scala, I declare a type C that extends B? Does C now have +, plus or both?

And what about multiple inheritance? If I extend both a Java interface with method plus and a Scala trait with a method alpha-annotated as plus, and the signature is identical otherwise, is that considered the same method or two different methods?

2 Likes

I need to shout out now. This was THE feature that turned me on to Dotty and game me hope after being jaded by hundreds of failed google searches of various symbolic operators. I hope that every library author uses this feature. Also, if we’re proposing alternate names, why not @fullName?

2 Likes

Everything depends on where the original symbol is defined, uses have no bearing on this.

Say, I declare a type A in Scala with a method + , to which I add the alpha notation “plus”.
Now I declare in Java a type B which extends A , and Java only sees plus , so B only has a method plus . Can Scala now see that B has a plus method, or does it see + instead?

It sees +.

What if then, in Scala, I declare a type C that extends B ? Does C now have + , plus or both?

It only has a + method. The idea is simple: So far, a Scala-defined function has implicitly two types: one that Java can see, and the other (sometimes more refined) that Scala can see. Technically, the two types are stored in different section of the binaries: generic signatures for Java consumption, Tasy for Scala.
All that changes is that the method now comes under two names for Java or for Scala consumption.

2 Likes

As a programmer, if I hear host, I think of a machine on a network, not so much of a host language or host platform. @hostName therefore puts me on the wrong leg. @encodeAs is ugly in itself, but may be obvious enough as @encodeAs("plus")

9 Likes

What worries me is that current behavior limits programmer too much.

What if programmer really want to have access for both methods in scala? It needs to drop @alpha entirely.

As shown in this code you cannot introduce this method to scala anymore (not directly):

//https://scastie.scala-lang.org/UQ0YwYWARVaEwYvrp6r5NA
import scala.annotation.alpha
case class Test(name:String) {
  @alpha("assert") def - (op: => Boolean) = { println(name + ":" + op) }
  def assert (op: => Boolean) = this - op //COMPILATION ERROR
}

Test("not sure what") - { someExpression().that(_.returns[Boolean]) }

I really thought that ‘@alpha(“someName”)’ will generate method to use in scala or at least it will not block exposing it.

1 Like

I propose to name it JvmName. And have a second annotation JsName and yet another for other targets. This way one can use a name which fits the target.
Moreover, I would like to use this annotation in cases of ambiguity on the byte code level instead of resorting to the ugly DummyImplicit. So not only for functions using symbolic names but for all

2 Likes

I’m confused. Are you saying you are hiding from Scala methods defined in Java, because they happen to have the same signature as a method that Scala made available to Java only?

Also, what happens if a method originates in both Java and Scala, via multiple inheritance?

That is not possible. The “erased name” must be the same on all platforms, because the erasure on non-JVM platforms is determined by the JVM erasure. There is no way around that.

IIRC, you can do that.

I think @encodeAs("plus") is not bad. Are there other proposals how to name this annotation?

After reading this many times, I’ve come to the conclusion that maybe the thing should work the opposite way: define a function with the name you want it exported and use @alias es or something for the Scala-only accessible symbolic names. This way, both can be accessed and the intuitivity remains.

4 Likes

If the goal is to force libraries to decide on only one way to call their methods, what is to stop libraries from just using some arbitrary prefix instead, and keeping both methods? In that case nothing but busywork has been achieved IMO.

Something like

trait Applicative[F[_]]:
  ...

  extension [A, B](fa: F[A]):
    @encodeAs("myLibProductRight") def *>(fb: F[B]): F[B] = fa.productRight(fb)
    
    def productRight(fb: F[B]): F[B] = (fa, fb).map2((_, b) => b)

Furthermore, I honestly don’t know if we will se more or less symbolic operators with this change. Could be less as this change discourages using them too much, could be more, as now library devs don’t need to be afraid that the name can’t be googled. Personally I like it how it is now, where I as the dev can decide what reads best. In the above example, usually that is *>, but sometimes I don’t want a symbolic infix operator, and therefore use productRight instead.

2 Likes

Or simply allow @alpha to define a second name with no restrictions, doesn’t matter if alphanumeric or not, visible everywhere.

Anything else doesn’t play nicely with mixed-language type hierarchies.

This would run afoul of the example given that stuff like

case class Complex(real: Double, im: Double) {
  def +(that: Complex) = Complex(real + that.real, im + that.im)
}

should be able to work without further complications.

Does that mean that this feature is supposed to be platform-independent? Then I would suggest dropping the requirement “The name given in an @alpha annotation must be a legal name for the defined entities on the host platform.”, and define a common set of valid names.

Yes, indeed, we should define a common set of valid names. In practice this is basically going to be the set of valid JVM bytecode names, I think, because that’s also what the other platforms’ IRs support.

2 Likes

The Scala collections library 2.13+ has adopted an approach to alias all symbolic names with alphabetic (e.g. ++= is always aliased to addAll/appendAll).

The same approach is adopted in Cats, ZIO and many newer libraries follow suit.

IMHO the community wants to have more ways to call operators, it’s already written code to do it, it’s already expected of you, as a library author, to either avoid operators entirely or add aliases.
An @alpha introducing symbols would just remove boilerplate from an existing best practice, not establish something new - and that’s in general the sought outcome for adding a new language feature to a mature language.

12 Likes