Removal of right-associative methods from the language

There is now a proposal to remove right-associative methods from the language on the table.

Giving it visibility.

Let’s collect feedback.

Removing stuff is clearly a simplification.

Spontaneously I don’t see downsides. But I’m very unsure whether I miss something.

What does the hive-mind think?

5 Likes

I’m pro.

Right associative methods is one of the most surprising feature and in the presence of extension methods it looks like a legacy for me.

While I am in favor, I am unsure for it to be possible

The first step would be to see what happens if we transform all existing right-associative methods into extension methods.
If the behavior remains the same (unlikely), then we have a shot at it, otherwise it might be impossible

2 Likes

Right associative methods are are great. However to me it clearly makes sense that a right associative method should take precedence over a left associative method, if these methods would otherwise have the same level of precedence.

Currently in my own collections I have to use “%:” for prepend to give it higher precedence than than the append element operator.

Do extension methods require additional imports to work? We’ve always had “extension methods” in the form of implicit classes, so I’m wondering what’s different amount the Scala 3 language feature that makes this possible when it was not possible before

1 Like

Extension methods are looked up in the same implicit scopes as implicit classes. Concretely, a right associative extension method A op_: B is looked up in the companion object of B.

class A
object A:
  extension (a: A) def ++ (y: B): Unit = ???

class B
object B:
  extension (a: A) def *: (y: B): Unit = ???

def test =
  val a = A()
  val b = B()
  a ++ b // works, looked up in A
  a *: b // works, looked up in B
1 Like

We’ve sort of forked the discussion here, so a lot more context in my last post on the other thread, but to be clear I don’t believe the proposal is to fully remove right-associativity from the language. But rather to remove what I referred to as “right-receptivity” (see link for details on that). But implied right-biased parenthetical grouping of chained operators would still be possible.

EDIT: oops… hit submit too early

So this would go away:

class Foo:
  def %:(b: Bar) = ???

val f: Foo = ???
val b: Bar = ???

b %: f

But this would stay:

val one = a :: b :: c :: d :: Nil
val two = a :: (b :: (c :: (d :: Nil)))

//one and two are constructed the same way

In my languishing PR to improve handling of right-associative ops in 2.13, which I’d hoped to get in before 2.14, I noticed that it’s tricky to anticipate evaluation order of right-associative infix ops in the face of dependent values and conversions.

I didn’t notice during that fructifying period that Scala 3 already rejects one of the characteristic tests for this ticket.

Even the “normal” application

mkFoo.andThen_:(mkBarString)

does not benefit from the conversion of the argument that is dependent on the Foo receiver.

The interesting case was supposed to be

mkBarString andThen_: mkFoo

where left-to-right evaluation is expected. (The improvement in my PR was that if the parameter is by-name, its evaluation is further deferred.)

image

I think that comment meant #:: which is when regularity of expectation starts to matter.

It’s probably a good thing if the user must write the application such that it corresponds to the definition, and conversely, the definition is written so that it looks like the application.

(That definition may look funky due to type params and using clauses.)

Sorry, I don’t follow. But I wasn’t paying attention to these issues until recently so maybe I missed something.

Wouldn’t the expectation here be right-to-left evaluation, not left-to-right? mkFoo is evaluated, then dereferenced to find andThen_:, then mkBarString is evaluated, converted, and applied?

If there’s complexity in compiler implementation around getting the order of implicit conversion, application, etc correct, I can definitely understand that. I haven’t written a compiler since college, which has been… a while. So I don’t think I can help resolve it, per se, but does this complexity of implementation rise to the level of rejection of an entire portion of the language? Particularly in Scala 3 which changes a lot around implicit resolution and right-associativity (like fixing the by-name issue).

Maybe? I’m not fully convinced, but for the sake of argument, let’s go with it. That’s not what a : suffix means in Scala, so if we prefer to change right-associativity to left-receptivity instead of right-receptivity we should introduce a new mechanism. Perhaps a new suffix? Or some other mechanism of flagging it along with a language deprecation of : suffixes. But breaking the definition of : suffixes, but only on extensions, is not ideal to say the least.

That is my real complaint. If we resolve that by un-breaking it, I would be very happy to engage in discussions about a replacement mechanism that is better, for some value of “better”. But, again, only in the context of potentially changing receptivity of the operator. Not fully removing right-associativity from Scala.

I don’t know about the expectation!

but the spec at the end of the section on infix ops says

If op is right-associative and its parameter is passed by value, it (i.e., e1;op;e2) is interpreted as { val x=e1; e2​.op(x) }, where x is a fresh name.

Ah! I see what you mean now. Thanks for clarifying.