Skip to content

SIP-70 - Flexible Varargs #105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open

Conversation

lihaoyi
Copy link
Contributor

@lihaoyi lihaoyi commented Feb 28, 2025

No description provided.

```

### Javascript
Javascript's expression `...` syntax works identically to this proposal. In Python, you can mix
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python should be Javascript

@som-snytt
Copy link

On the pattern side, the old gotcha is scala/bug#7623 (which has a lint on Scala 2). That is where two pat vars are satisfied by a single Seq, i.e., there is a kind of misalignment. (The example may temper ambition for patterns.)

@Atry
Copy link
Contributor

Atry commented Mar 6, 2025

Do you want to cite #41 ? There are a lot of previous discussions on this feature.

This SIP is a renamed version of #41 , and also adds an intermediate step to create a temporary seq.

  • applyBegin -> newBuilder
  • applyNext -> addOne
  • applyNextSeq -> addAll
  • applyEnd -> result

@kyouko-taiga kyouko-taiga changed the title SIP-XX - Flexible Varargs SIP-70 - Flexible Varargs Mar 21, 2025
@prolativ
Copy link
Contributor

I started to wander if this SIP could help resolve this issue: scala/scala3#18009
In short, the problem there is that an invocation of a curried method added by a refinement on a subtype of Selectable, e.g.

mySelectable.foo(args1*)(args2*)

should get desugared to something like

mySelectable.applyDynamic("foo")(args1*, args2*).asInstanceOf[Foo]

which is now illegal, but would be when this SIP gets implemented

@lihaoyi
Copy link
Contributor Author

lihaoyi commented Jun 29, 2025

@odersky @sjrd @lrytz have any of you had a chance to look at this? It's been a month since reviewers were assigned so I'm hoping to get some feedback

Copy link
Contributor

@odersky odersky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's proposed:

  1. Constructing varargs with multiple splices xs*, interspersed with normal values
  2. Deconstructing sequences with a single vararg pattern that can be somewhere in the middle of the sequence, not just at the end.

These are clear quality of life improvements. The construction part is the more important one, and it should be straightforward to implement. The pattern matching part is probably also easy to implement, but it might need some care to get good performance for linear sequences. E.g you have a long list and match with List(x, y, ys*, 2, 3) you want to avoid multiple trips to the end of the list to match the 2 and 3. Nevertheless, it looks quite doable.

@lrytz
Copy link
Member

lrytz commented Jul 2, 2025

I also think this SIP is a very nice improvement.

You mention / suggest to use IArray, which is an opaque type alias and not part of the collection hierarchy. sum(IArray.newBuilder[Int].addOne(1).result()*) wraps the resulting array using IArray.wrapIntArray, which converts it to an immutable.ArraySeq. So it's probably better to use ArraySeq directly. Or maybe Vector?

  • ArraySeq: can wrap a primitive array, minimal memory overhead
  • Vector: better if the collections is later used for appending / prepending, concatenation etc. Memory and computational overhead is small. Primitives are boxed.

I'm not sure if the newBuilder call needs an explicit type argument, or if we can have that inferred by the typer. In Scala 3:

scala> collection.immutable.ArraySeq.newBuilder.addOne(1).result()
-- [E172] Type Error: ----------------------------------------------------------
1 |collection.immutable.ArraySeq.newBuilder.addOne(1).result()
  |                                        ^
  |                                        No ClassTag available for Any
1 error found

Interestingly, the same line works on Scala 2, the compiler infers newBuilder[Int]. Is that a known limitation @odersky?

If an explicit type argument is needed, the SIP should specify how that type is obtained.


For patterns, we'll need to update the spec to say how case E(p1, ..., pN, s*, u1, ..., uM) translates. I think the section is https://scala-lang.org/files/archive/spec/3.4/08-pattern-matching.html#pattern-sequences, the spec seems not to be fully up to date though, it doesn't reflect scala/scala3#11240.

This SIP doesn't change repeated parameters (of case classes), a repeated parameter will still be last in a case classs definition. An unapplySeq declaration should also continue to have the corresponding return type (T1, ..., Tm, Seq[S]).

So the spec needs to map the new pattern shape onto to current case class parameter list shape (or unapplySeq return type shape). Note that T1, ..., Tm can be arbitrary types, all different from each other and different from S. So I'm not sure the proposal of translating case E(p1, ..., pN, s*, u1, ..., uM) to case VarargsMatcher(Seq(p1, ..., pN), s, Seq(u1, ..., uM)) would work.

@lihaoyi
Copy link
Contributor Author

lihaoyi commented Jul 7, 2025

You mention / suggest to use IArray, which is an opaque type alias and not part of the collection hierarchy. sum(IArray.newBuilder[Int].addOne(1).result()*) wraps the resulting array using IArray.wrapIntArray, which converts it to an immutable.ArraySeq. So it's probably better to use ArraySeq directly. Or maybe Vector?

The motivation for IArray was to try and decouple the language feature from the concrete implementation of the Scala collections library: IArray seems like a relatively simple collection with relatively good performance characteristics. In particular, I wanted to:

  • Avoid using Seq which defaults to List which is from the start a lot less cache-friendly than IArray.
  • Vector has the downside of being a pretty specific collection in the standard library, with a very-non-trivial implementation, and AFAIK is something the Scala language was not aware of up to this point.

This SIP doesn't change repeated parameters (of case classes), a repeated parameter will still be last in a case classs definition.

Yes, definitions are unchanged. Not just for case classes, but for method defs as well, they all can only have one vararg* parameter at the end

An unapplySeq declaration should also continue to have the corresponding return type (T1, ..., Tm, Seq[S]).

Yes that is right. The trailing Seq[S] can be broken up into a smaller Seq in the middle and loose elements before/after it during destructuring, but these elements all continue to be homogeneously typed as S, and the signature of the unapplySeq does not need to change

@odersky
Copy link
Contributor

odersky commented Jul 7, 2025

Interestingly, the same line works on Scala 2, the compiler infers newBuilder[Int]. Is that a known limitation @odersky?

No, I did not know that.

Generally, I think it's better of the SIP does not specify a precise implementation scheme. The scheme might change in the future, and we should not have to change the SIP because of that. The IArray code can be considered for illustration, of course.

In terms of what we have, it looks like basing the whole thing on ArrayBuilder might work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants