-
Notifications
You must be signed in to change notification settings - Fork 28
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
base: main
Are you sure you want to change the base?
Conversation
content/flexible-varargs.md
Outdated
``` | ||
|
||
### Javascript | ||
Javascript's expression `...` syntax works identically to this proposal. In Python, you can mix |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Python should be Javascript
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.) |
I started to wander if this SIP could help resolve this issue: scala/scala3#18009 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's proposed:
- Constructing varargs with multiple splices xs*, interspersed with normal values
- 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.
I also think this SIP is a very nice improvement. You mention / suggest to use
I'm not sure if the 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 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 This SIP doesn't change repeated parameters (of case classes), a repeated parameter will still be last in a case classs definition. An 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 |
The motivation for
Yes, definitions are unchanged. Not just for
Yes that is right. The trailing |
Co-authored-by: odersky <[email protected]>
Co-authored-by: odersky <[email protected]>
Co-authored-by: odersky <[email protected]>
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 In terms of what we have, it looks like basing the whole thing on |
It's The spec says when using a sequence argument
Right. I was thinking what happens when using subpatterns and then got a bit lost in spec. The def f(x: Any): String = x match {
case Seq(Some(_), y: String, xs*, 1) => y
}
// translates to
def f(x: Any): String = {
val VarargsMatcher = new VarargsMatchHelper(2, 1)
x match {
case VarargsMatcher(Seq(Some(_), y: String), xs, Seq(1)) => y
}
} |
There is now an implementation of most of the proposal. One doubt I am having is spreads of def foo(x: Int*) = x.sum
val xo = Some(2)
f(xo*) That's an error since the argument of a spread operator must be a f(xo.toList*) would work today. I'd be interested in getting the committee member's opinion on this aspect before I try to implement it. |
My motivation for including a special case for The use of a naked A third option is to allow splicing options in longer sequences such as So IMO might as well allow |
Perhaps a fourth alternative is that we add a new marker trait trait VarargUnpackable[+T]{
// Used when unpacking a single value, can
// efficiently return `this` for `Seq`s
def toSeq: Seq[T]
// Used when unpacking flexible varargs to
// efficiently feed the elements of this object
// into a `Seq.builder`
def copyToArray(dest: Array[T], startIndex: Int): Unit
// Used when unpacking flexible varargs to allow
// the builder to expand its capacity precisely
// when the size of this is known. Otherwise
// returns `-1` indicating the size it not known
// and the builder would have to handle expansion
// using its own heuristics
def sizeHint: Int
// When sizeHint is -1, we cannot pre-size the
// builder array, and so we append elements one
// at a time to give the builder a chance to resize
// as necessary
def foreach(f: T => Unit): Unit
}
object VarargUnpackable{
class Builder[T]{
def addAll(v: VarargUnpackable[T])
def add(v: T)
def result(): Seq[T]
}
} // Desugaring
foo(bar*)
foo(VarargUnpackable.Builder().addAll(bar).build())
foo(bar*, qux*)
foo(VarargUnpackable.Builder().addAll(bar).addAll(qux).build())
foo(value1, bar*, value2, qux*, value3)
foo(VarargUnpackable.Builder().add(value1).addAll(bar).add(value2).addAll(qux).add(value3).build()) The That would avoid hardcoding A fifth alternative would be to use a name-based desugaring so |
Why not just let |
@Atry I think it would be good to have the language feature depend on as minimal an interface as possible, so a minimal |
I think all this goes to far. Varargs are Seq's. I see no reason to complicate things further here. Arrays can be passed to Seq's since there is an implicit conversion. The point of SIP 70 is to stay within this framework and allow multiple spreads. My PR provides that. Options unfortunately are an irregular case since there is no conversion from option to Seq (and for good reason!). I think it is much cleaner to keep that principle even if some use cases require an explicit Here's another way to put it: I can have a vararg function and a spread argument to it: def f(x: Int*)
f(xs*) I can convert the vararg to a def f(x: Seq[Int])
f(xs) That works, always. I have done that refactoring many times. But allowing options in spreads breaks it. Generally varargs are tricky to get right since they need to be treated early during type inference when types are not yet known completely. That compounds complexity. So we need to keep it simple. |
I agree with @odersky. I don't question that there are use cases for spreading |
I think it would indeed be interesting if we had a minimal interface for varargs and spreads instead of the |
As a long-term Scala user, I strongly support @lihaoyi's arguments. |
Maybe we can add If we allowed a spread type then we could use a unary |
No description provided.