Skip to content

Commit b26a4ac

Browse files
Merge pull request #7732 from brunnerant/tuple-split
Extend Tuple API
2 parents 394217e + 77a4496 commit b26a4ac

File tree

16 files changed

+372
-8
lines changed

16 files changed

+372
-8
lines changed

bench-run/inputs/drop.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
size:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50

bench-run/inputs/split.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
size:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50

bench-run/inputs/take.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
size:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50

bench-run/src/main/scala/dotty/tools/benchmarks/tuples/TupleOps.scala

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package dotty.tools.benchmarks.tuples
22

33
import org.openjdk.jmh.annotations._
4+
import scala.util.Random
45

56
@State(Scope.Thread)
67
class TupleOps {
78
var tuple1: Tuple = _
89
var tuple2: Tuple = _
10+
var tuple3: Tuple = _
911

1012
@Setup
1113
def setup(): Unit = {
@@ -16,6 +18,9 @@ class TupleOps {
1618
tuple2 = ()
1719
for (i <- 1 until 10)
1820
tuple2 = s"elem$i" *: tuple2
21+
22+
val rand = new Random(12345)
23+
tuple3 = Tuple.fromArray(rand.shuffle(1 to 15).toArray)
1924
}
2025

2126
def tupleFlatMap(tuple: Tuple, f: [A] => A => Tuple): Tuple = {
@@ -34,6 +39,22 @@ class TupleOps {
3439
tailRecReverse(tuple, ())
3540
}
3641

42+
def tupleMerge(tuple1: Tuple, tuple2: Tuple): Tuple = (tuple1, tuple2) match {
43+
case (_, ()) => tuple1
44+
case ((), _) => tuple2
45+
case (x *: xs, y *: ys) =>
46+
if (x.asInstanceOf[Int] <= y.asInstanceOf[Int]) x *: tupleMerge(xs, tuple2)
47+
else y *: tupleMerge(tuple1, ys)
48+
}
49+
50+
def tupleMergeSort(tuple: Tuple): Tuple =
51+
if (tuple.size <= 1) tuple
52+
else {
53+
val (tuple1, tuple2) = tuple.splitAt(tuple.size / 2)
54+
val (sorted1, sorted2) = (tupleMergeSort(tuple1), tupleMergeSort(tuple2))
55+
tupleMerge(sorted1, sorted2)
56+
}
57+
3758
@Benchmark
3859
def reverse(): Tuple = {
3960
tupleReverse(tuple1)
@@ -43,4 +64,9 @@ class TupleOps {
4364
def flatMap(): Tuple = {
4465
tupleFlatMap(tuple2, [A] => (x: A) => (x, x))
4566
}
67+
68+
@Benchmark
69+
def mergeSort(): Tuple = {
70+
tupleMergeSort(tuple3)
71+
}
4672
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package dotty.tools.benchmarks.tuples
2+
3+
import org.openjdk.jmh.annotations._
4+
import scala.runtime.DynamicTuple
5+
6+
@State(Scope.Thread)
7+
class Drop {
8+
@Param(Array("0"))
9+
var size: Int = _
10+
var tuple: Tuple = _
11+
var array: Array[Object] = _
12+
var half: Int = _
13+
14+
@Setup
15+
def setup(): Unit = {
16+
tuple = ()
17+
half = size / 2
18+
19+
for (i <- 1 to size)
20+
tuple = "elem" *: tuple
21+
22+
array = new Array[Object](size)
23+
}
24+
25+
@Benchmark
26+
def tupleDrop(): Tuple = {
27+
DynamicTuple.dynamicDrop(tuple, half)
28+
}
29+
30+
@Benchmark
31+
def arrayDrop(): Array[Object] = {
32+
array.drop(half)
33+
}
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package dotty.tools.benchmarks.tuples
2+
3+
import org.openjdk.jmh.annotations._
4+
import scala.runtime.DynamicTuple
5+
6+
@State(Scope.Thread)
7+
class Split {
8+
@Param(Array("0"))
9+
var size: Int = _
10+
var tuple: Tuple = _
11+
var array: Array[Object] = _
12+
var half: Int = _
13+
14+
@Setup
15+
def setup(): Unit = {
16+
tuple = ()
17+
half = size / 2
18+
19+
for (i <- 1 to size)
20+
tuple = "elem" *: tuple
21+
22+
array = new Array[Object](size)
23+
}
24+
25+
@Benchmark
26+
def tupleSplit(): (Tuple, Tuple) = {
27+
DynamicTuple.dynamicSplitAt(tuple, half)
28+
}
29+
30+
@Benchmark
31+
def arraySplit(): (Array[Object], Array[Object]) = {
32+
array.splitAt(half)
33+
}
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package dotty.tools.benchmarks.tuples
2+
3+
import org.openjdk.jmh.annotations._
4+
import scala.runtime.DynamicTuple
5+
6+
@State(Scope.Thread)
7+
class Take {
8+
@Param(Array("0"))
9+
var size: Int = _
10+
var tuple: Tuple = _
11+
var array: Array[Object] = _
12+
var half: Int = _
13+
14+
@Setup
15+
def setup(): Unit = {
16+
tuple = ()
17+
half = size / 2
18+
19+
for (i <- 1 to size)
20+
tuple = "elem" *: tuple
21+
22+
array = new Array[Object](size)
23+
}
24+
25+
@Benchmark
26+
def tupleTake(): Tuple = {
27+
DynamicTuple.dynamicTake(tuple, half)
28+
}
29+
30+
@Benchmark
31+
def arrayTake(): Array[Object] = {
32+
array.take(half)
33+
}
34+
}

compiler/test/dotc/run-test-pickling.blacklist

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ t7374
66
tuples1.scala
77
tuples1a.scala
88
tuples1b.scala
9+
tuple-ops.scala
10+
tuple-take.scala
11+
tuple-drop.scala
12+
tuple-zip.scala
913
typeclass-derivation1.scala
1014
typeclass-derivation2.scala
1115
typeclass-derivation2a.scala
@@ -19,4 +23,3 @@ t10889
1923
macros-in-same-project1
2024
i5257.scala
2125
enum-java
22-
tuple-ops.scala

library/src/scala/Tuple.scala

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,33 @@ sealed trait Tuple extends Any {
4343
inline def zip[This >: this.type <: Tuple, T2 <: Tuple](t2: T2): Zip[This, T2] =
4444
DynamicTuple.dynamicZip(this, t2)
4545

46-
/** Called on a tuple `(a1, ..., an)`, returns a new tuple `(f(a1), ..., f(an))`.
47-
* The result is typed as `(F[A1], ..., F[An])` if the tuple type is fully known.
48-
* If the tuple is of the form `a1 *: ... *: Tuple` (that is, the tail is not known
49-
* to be the cons type.
50-
*/
46+
/** Called on a tuple `(a1, ..., an)`, returns a new tuple `(f(a1), ..., f(an))`.
47+
* The result is typed as `(F[A1], ..., F[An])` if the tuple type is fully known.
48+
* If the tuple is of the form `a1 *: ... *: Tuple` (that is, the tail is not known
49+
* to be the cons type.
50+
*/
5151
inline def map[F[_]](f: [t] => t => F[t]): Map[this.type, F] =
5252
DynamicTuple.dynamicMap(this, f)
53+
54+
/** Given a tuple `(a1, ..., am)`, returns the tuple `(a1, ..., an)` consisting
55+
* of its first n elements.
56+
*/
57+
inline def take[This >: this.type <: Tuple](n: Int): Take[This, n.type] =
58+
DynamicTuple.dynamicTake[This, n.type](this, n)
59+
60+
61+
/** Given a tuple `(a1, ..., am)`, returns the tuple `(an+1, ..., am)` consisting
62+
* all its elements except the first n ones.
63+
*/
64+
inline def drop[This >: this.type <: Tuple](n: Int): Drop[This, n.type] =
65+
DynamicTuple.dynamicDrop[This, n.type](this, n)
66+
67+
/** Given a tuple `(a1, ..., am)`, returns a pair of the tuple `(a1, ..., an)`
68+
* consisting of the first n elements, and the tuple `(an+1, ..., am)` consisting
69+
* of the remaining elements.
70+
*/
71+
inline def splitAt[This >: this.type <: Tuple](n: Int): Split[This, n.type] =
72+
DynamicTuple.dynamicSplitAt[This, n.type](this, n)
5373
}
5474

5575
object Tuple {
@@ -116,6 +136,29 @@ object Tuple {
116136
*/
117137
type IsMappedBy[F[_]] = [X <: Tuple] =>> X =:= Map[InverseMap[X, F], F]
118138

139+
/** Transforms a tuple `(T1, ..., Tn)` into `(T1, ..., Ti)`. */
140+
type Take[T <: Tuple, N <: Int] <: Tuple = N match {
141+
case 0 => Unit
142+
case S[n1] => T match {
143+
case Unit => Unit
144+
case x *: xs => x *: Take[xs, n1]
145+
}
146+
}
147+
148+
/** Transforms a tuple `(T1, ..., Tn)` into `(Ti+1, ..., Tn)`. */
149+
type Drop[T <: Tuple, N <: Int] <: Tuple = N match {
150+
case 0 => T
151+
case S[n1] => T match {
152+
case Unit => Unit
153+
case x *: xs => Drop[xs, n1]
154+
}
155+
}
156+
157+
/** Splits a tuple (T1, ..., Tn) into a pair of two tuples `(T1, ..., Ti)` and
158+
* `(Ti+1, ..., Tn)`.
159+
*/
160+
type Split[T <: Tuple, N <: Int] = (Take[T, N], Drop[T, N])
161+
119162
/** Convert an array into a tuple of unknown arity and types */
120163
def fromArray[T](xs: Array[T]): Tuple = {
121164
val xs2 = xs match {

library/src/scala/runtime/DynamicTuple.scala

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package scala.runtime
22

3-
import scala.Tuple.{ Concat, Size, Head, Tail, Elem, Zip, Map }
3+
import scala.Tuple.{ Concat, Size, Head, Tail, Elem, Zip, Map, Take, Drop, Split }
44

55
object DynamicTuple {
66
inline val MaxSpecialized = 22
@@ -380,7 +380,7 @@ object DynamicTuple {
380380
case _ => specialCaseTail(self)
381381
}
382382

383-
def dynamicApply[This <: NonEmptyTuple, N <: Int] (self: This, n: Int): Elem[This, N] = {
383+
def dynamicApply[This <: NonEmptyTuple, N <: Int] (self: This, n: N): Elem[This, N] = {
384384
type Result = Elem[This, N]
385385
val res = (self: Any) match {
386386
case self: Unit => throw new IndexOutOfBoundsException(n.toString)
@@ -473,6 +473,78 @@ object DynamicTuple {
473473
.asInstanceOf[Map[This, F]]
474474
}
475475

476+
def dynamicTake[This <: Tuple, N <: Int](self: This, n: N): Take[This, N] = {
477+
if (n < 0) throw new IndexOutOfBoundsException(n.toString)
478+
val actualN = Math.min(n, self.size)
479+
480+
type Result = Take[This, N]
481+
482+
if (actualN == 0) ().asInstanceOf[Result]
483+
else {
484+
val arr = (self: Any) match {
485+
case xxl: TupleXXL =>
486+
xxl.elems.asInstanceOf[Array[Object]].take(actualN)
487+
case _ =>
488+
val arr = new Array[Object](actualN)
489+
self.asInstanceOf[Product].productIterator.asInstanceOf[Iterator[Object]]
490+
.copyToArray(arr, 0, actualN)
491+
arr
492+
}
493+
494+
dynamicFromIArray(arr.asInstanceOf[IArray[Object]]).asInstanceOf[Result]
495+
}
496+
}
497+
498+
def dynamicDrop[This <: Tuple, N <: Int](self: This, n: N): Drop[This, N] = {
499+
if (n < 0) throw new IndexOutOfBoundsException(n.toString)
500+
val size = self.size
501+
val actualN = Math.min(n, size)
502+
val rem = size - actualN
503+
504+
type Result = Drop[This, N]
505+
506+
if (rem == 0) ().asInstanceOf[Result]
507+
else {
508+
val arr = (self: Any) match {
509+
case xxl: TupleXXL =>
510+
xxl.elems.asInstanceOf[Array[Object]].drop(actualN)
511+
case _ =>
512+
val arr = new Array[Object](rem)
513+
self.asInstanceOf[Product].productIterator.asInstanceOf[Iterator[Object]]
514+
.drop(actualN).copyToArray(arr, 0, rem)
515+
arr
516+
}
517+
518+
dynamicFromIArray(arr.asInstanceOf[IArray[Object]]).asInstanceOf[Result]
519+
}
520+
}
521+
522+
def dynamicSplitAt[This <: Tuple, N <: Int](self: This, n: N): Split[This, N] = {
523+
if (n < 0) throw new IndexOutOfBoundsException(n.toString)
524+
val size = self.size
525+
val actualN = Math.min(n, size)
526+
527+
type Result = Split[This, N]
528+
529+
val (arr1, arr2) = (self: Any) match {
530+
case () => (Array.empty[Object], Array.empty[Object])
531+
case xxl: TupleXXL =>
532+
xxl.elems.asInstanceOf[Array[Object]].splitAt(actualN)
533+
case _ =>
534+
val arr1 = new Array[Object](actualN)
535+
val arr2 = new Array[Object](size - actualN)
536+
val it = self.asInstanceOf[Product].productIterator.asInstanceOf[Iterator[Object]]
537+
it.copyToArray(arr1, 0, actualN)
538+
it.copyToArray(arr2, 0, size - actualN)
539+
(arr1, arr2)
540+
}
541+
542+
(
543+
dynamicFromIArray(arr1.asInstanceOf[IArray[Object]]),
544+
dynamicFromIArray(arr2.asInstanceOf[IArray[Object]])
545+
).asInstanceOf[Result]
546+
}
547+
476548
def consIterator(head: Any, tail: Tuple): Iterator[Any] =
477549
Iterator.single(head) ++ tail.asInstanceOf[Product].productIterator
478550

tests/run/tuple-drop.check

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
()
2+
()
3+
()
4+
(1,2,3,4,5)
5+
(2,3,4,5)
6+
(4,5)
7+
()
8+
()
9+
(11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35)
10+
(12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35)
11+
(21,22,23,24,25,26,27,28,29,30,31,32,33,34,35)
12+
(31,32,33,34,35)
13+
()
14+
()

tests/run/tuple-drop.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
object Test extends App {
3+
val emptyTuple: Tuple = ()
4+
val tuple: Tuple = ("1", "2", "3", "4", "5")
5+
val tupleXXL: Tuple = ("11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35")
6+
7+
println(emptyTuple.drop(0))
8+
println(emptyTuple.drop(1))
9+
println(emptyTuple.drop(10))
10+
11+
println(tuple.drop(0))
12+
println(tuple.drop(1))
13+
println(tuple.drop(3))
14+
println(tuple.drop(5))
15+
println(tuple.drop(10))
16+
17+
println(tupleXXL.drop(0))
18+
println(tupleXXL.drop(1))
19+
println(tupleXXL.drop(10))
20+
println(tupleXXL.drop(20))
21+
println(tupleXXL.drop(25))
22+
println(tupleXXL.drop(30))
23+
}

0 commit comments

Comments
 (0)