Skip to content

Commit 907dfcf

Browse files
committed
Merge pull request #66 from szeiger/wip/widening
Automatic widening of numeric types for primitive Streams
2 parents b175a03 + f029ed5 commit 907dfcf

File tree

6 files changed

+193
-37
lines changed

6 files changed

+193
-37
lines changed

README.md

+12-7
Original file line numberDiff line numberDiff line change
@@ -121,22 +121,27 @@ class Test {
121121

122122
Scala collections gain `seqStream` and `parStream` as extension methods that produce a Java 8 Stream
123123
running sequentially or in parallel, respectively. These are automatically specialized to a primitive
124-
type if possible. For instance, `List(1,2).seqStream` produces an `IntStream`. Maps additionally have
124+
type if possible, including automatically applied widening conversions. For instance, `List(1,2).seqStream`
125+
produces an `IntStream`, and so does `List(1.toShort, 2.toShort).parStream`. Maps additionally have
125126
`seqKeyStream`, `seqValueStream`, `parKeyStream`, and `parValueStream` methods.
126127

127128
Scala collections also gain `accumulate` and `stepper` methods that produce utility collections that
128129
can be useful when working with Java 8 Streams. `accumulate` produces an `Accumulator` or its primitive
129130
counterpart (`DoubleAccumulator`, etc.), which is a low-level collection designed for efficient collection
130131
and dispatching of results to and from Streams. Unlike most collections, it can contain more than
131-
`Int.MaxValue` elements. `stepper` produces a `Stepper` which is a fusion of `Spliterator` and `Iterator`.
132-
`Stepper`s underlie the Scala collections' instances of Java 8 Streams.
132+
`Int.MaxValue` elements.
133+
134+
`stepper` produces a `Stepper` which is a fusion of `Spliterator` and `Iterator`. `Stepper`s underlie the Scala
135+
collections' instances of Java 8 Streams. Steppers are intended as low-level building blocks for streams.
136+
Usually you would not create them directly or call their methods but you can implement them alongside custom
137+
collections to get better performance when streaming from these collections.
133138

134139
Java 8 Streams gain `toScala[Coll]` and `accumulate` methods, to make it easy to produce Scala collections
135140
or Accumulators, respectively, from Java 8 Streams. For instance, `myStream.to[Vector]` will collect the
136141
contents of a Stream into a `scala.collection.immutable.Vector`. Note that standard sequential builders
137142
are used for collections, so this is best done to gather the results of an expensive computation.
138143

139-
Finally, there is a Java class, `ScalaStreamer`, that has a series of `from` methods that can be used to
144+
Finally, there is a Java class, `ScalaStreamSupport`, that has a series of `stream` methods that can be used to
140145
obtain Java 8 Streams from Scala collections from within Java.
141146

142147
#### Performance Considerations
@@ -218,7 +223,7 @@ def mapToSortedString[A](xs: Vector[A], f: A => String, sep: String) =
218223
#### Java Usage Example
219224

220225
To convert a Scala collection to a Java 8 Stream from within Java, it usually
221-
suffices to call `ScalaStreaming.from(xs)` on your collection `xs`. If `xs` is
226+
suffices to call `ScalaStreamSupport.stream(xs)` on your collection `xs`. If `xs` is
222227
a map, you may wish to get the keys or values alone by using `fromKeys` or
223228
`fromValues`. If the collection has an underlying representation that is not
224229
efficiently parallelized (e.g. `scala.collection.immutable.List`), then
@@ -237,14 +242,14 @@ Here is an example of conversion of a Scala collection within Java 8:
237242

238243
```java
239244
import scala.collection.mutable.ArrayBuffer;
240-
import scala.compat.java8.ScalaStreaming;
245+
import scala.compat.java8.ScalaStreamSupport;
241246

242247
public class StreamConvertersExample {
243248
public int MakeAndUseArrayBuffer() {
244249
ArrayBuffer<String> ab = new ArrayBuffer<String>();
245250
ab.$plus$eq("salmon");
246251
ab.$plus$eq("herring");
247-
return ScalaStreaming.from(ab).mapToInt(x -> x.length()).sum(); // 6+7 = 13
252+
return ScalaStreamSupport.stream(ab).mapToInt(x -> x.length()).sum(); // 6+7 = 13
248253
}
249254
}
250255
```

src/main/scala/scala/compat/java8/StreamConverters.scala

+86-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ trait PrimitiveStreamUnboxer[A, S] {
1414
def apply(boxed: Stream[A]): S
1515
}
1616

17-
trait Priority3StreamConverters {
17+
trait Priority4StreamConverters {
18+
// Fallback converters for AnySteppers that cannot be unboxed and widened to primitive streams
1819
implicit class EnrichAnySteppableWithParStream[A, CC](cc: CC)(implicit steppize: CC => MakesStepper[AnyStepper[A] with EfficientSubstep])
1920
extends MakesParallelStream[A, Stream[A]] {
2021
def parStream: Stream[A] = StreamSupport.stream(steppize(cc).stepper.anticipateParallelism, true)
@@ -25,7 +26,6 @@ trait Priority3StreamConverters {
2526
implicit class EnrichAnyValueSteppableWithParValueStream[V, CC](cc: CC)(implicit steppize: CC => MakesValueStepper[AnyStepper[V] with EfficientSubstep]) {
2627
def parValueStream: Stream[V] = StreamSupport.stream(steppize(cc).valueStepper.anticipateParallelism, true)
2728
}
28-
// Note--conversion is only to make sure implicit conversion priority is lower than alternatives.
2929
implicit class EnrichScalaCollectionWithSeqStream[A, CC](cc: CC)(implicit steppize: CC => MakesStepper[AnyStepper[A]])
3030
extends MakesSequentialStream[A, Stream[A]] {
3131
def seqStream: Stream[A] = StreamSupport.stream(steppize(cc).stepper, false)
@@ -38,6 +38,90 @@ trait Priority3StreamConverters {
3838
}
3939
}
4040

41+
trait Priority3StreamConverters extends Priority4StreamConverters {
42+
// Prefer to unbox and widen small primitive types over keeping them boxed
43+
implicit class EnrichBoxedFloatSteppableWithParStream[CC](cc: CC)(implicit steppize: CC => MakesStepper[AnyStepper[Float] with EfficientSubstep])
44+
extends MakesParallelStream[java.lang.Double, DoubleStream] {
45+
def parStream: DoubleStream = StreamSupport.doubleStream(new Stepper.WideningFloatStepper(steppize(cc).stepper.anticipateParallelism), true)
46+
}
47+
implicit class EnrichBoxedFloatKeySteppableWithParKeyStream[CC](cc: CC)(implicit steppize: CC => MakesKeyStepper[AnyStepper[Float] with EfficientSubstep]) {
48+
def parKeyStream: DoubleStream = StreamSupport.doubleStream(new Stepper.WideningFloatStepper(steppize(cc).keyStepper.anticipateParallelism), true)
49+
}
50+
implicit class EnrichBoxedFloatValueSteppableWithParValueStream[CC](cc: CC)(implicit steppize: CC => MakesValueStepper[AnyStepper[Float] with EfficientSubstep]) {
51+
def parValueStream: DoubleStream = StreamSupport.doubleStream(new Stepper.WideningFloatStepper(steppize(cc).valueStepper.anticipateParallelism), true)
52+
}
53+
implicit class EnrichBoxedByteSteppableWithParStream[CC](cc: CC)(implicit steppize: CC => MakesStepper[AnyStepper[Byte] with EfficientSubstep])
54+
extends MakesParallelStream[java.lang.Integer, IntStream] {
55+
def parStream: IntStream = StreamSupport.intStream(new Stepper.WideningByteStepper(steppize(cc).stepper.anticipateParallelism), true)
56+
}
57+
implicit class EnrichBoxedByteKeySteppableWithParKeyStream[CC](cc: CC)(implicit steppize: CC => MakesKeyStepper[AnyStepper[Byte] with EfficientSubstep]) {
58+
def parKeyStream: IntStream = StreamSupport.intStream(new Stepper.WideningByteStepper(steppize(cc).keyStepper.anticipateParallelism), true)
59+
}
60+
implicit class EnrichBoxedByteValueSteppableWithParValueStream[CC](cc: CC)(implicit steppize: CC => MakesValueStepper[AnyStepper[Byte] with EfficientSubstep]) {
61+
def parValueStream: IntStream = StreamSupport.intStream(new Stepper.WideningByteStepper(steppize(cc).valueStepper.anticipateParallelism), true)
62+
}
63+
implicit class EnrichBoxedShortSteppableWithParStream[CC](cc: CC)(implicit steppize: CC => MakesStepper[AnyStepper[Short] with EfficientSubstep])
64+
extends MakesParallelStream[java.lang.Integer, IntStream] {
65+
def parStream: IntStream = StreamSupport.intStream(new Stepper.WideningShortStepper(steppize(cc).stepper.anticipateParallelism), true)
66+
}
67+
implicit class EnrichBoxedShortKeySteppableWithParKeyStream[CC](cc: CC)(implicit steppize: CC => MakesKeyStepper[AnyStepper[Short] with EfficientSubstep]) {
68+
def parKeyStream: IntStream = StreamSupport.intStream(new Stepper.WideningShortStepper(steppize(cc).keyStepper.anticipateParallelism), true)
69+
}
70+
implicit class EnrichBoxedShortValueSteppableWithParValueStream[CC](cc: CC)(implicit steppize: CC => MakesValueStepper[AnyStepper[Short] with EfficientSubstep]) {
71+
def parValueStream: IntStream = StreamSupport.intStream(new Stepper.WideningShortStepper(steppize(cc).valueStepper.anticipateParallelism), true)
72+
}
73+
implicit class EnrichBoxedCharSteppableWithParStream[CC](cc: CC)(implicit steppize: CC => MakesStepper[AnyStepper[Char] with EfficientSubstep])
74+
extends MakesParallelStream[java.lang.Integer, IntStream] {
75+
def parStream: IntStream = StreamSupport.intStream(new Stepper.WideningCharStepper(steppize(cc).stepper.anticipateParallelism), true)
76+
}
77+
implicit class EnrichBoxedCharKeySteppableWithParKeyStream[CC](cc: CC)(implicit steppize: CC => MakesKeyStepper[AnyStepper[Char] with EfficientSubstep]) {
78+
def parKeyStream: IntStream = StreamSupport.intStream(new Stepper.WideningCharStepper(steppize(cc).keyStepper.anticipateParallelism), true)
79+
}
80+
implicit class EnrichBoxedCharValueSteppableWithParValueStream[CC](cc: CC)(implicit steppize: CC => MakesValueStepper[AnyStepper[Char] with EfficientSubstep]) {
81+
def parValueStream: IntStream = StreamSupport.intStream(new Stepper.WideningCharStepper(steppize(cc).valueStepper.anticipateParallelism), true)
82+
}
83+
implicit class EnrichBoxedFloatSteppableWithSeqStream[CC](cc: CC)(implicit steppize: CC => MakesStepper[AnyStepper[Float]])
84+
extends MakesSequentialStream[java.lang.Double, DoubleStream] {
85+
def seqStream: DoubleStream = StreamSupport.doubleStream(new Stepper.WideningFloatStepper(steppize(cc).stepper), false)
86+
}
87+
implicit class EnrichBoxedFloatKeySteppableWithSeqKeyStream[CC](cc: CC)(implicit steppize: CC => MakesKeyStepper[AnyStepper[Float]]) {
88+
def seqKeyStream: DoubleStream = StreamSupport.doubleStream(new Stepper.WideningFloatStepper(steppize(cc).keyStepper), false)
89+
}
90+
implicit class EnrichBoxedFloatValueSteppableWithSeqValueStream[CC](cc: CC)(implicit steppize: CC => MakesValueStepper[AnyStepper[Float]]) {
91+
def seqValueStream: DoubleStream = StreamSupport.doubleStream(new Stepper.WideningFloatStepper(steppize(cc).valueStepper), false)
92+
}
93+
implicit class EnrichBoxedByteSteppableWithSeqStream[CC](cc: CC)(implicit steppize: CC => MakesStepper[AnyStepper[Byte]])
94+
extends MakesSequentialStream[java.lang.Integer, IntStream] {
95+
def seqStream: IntStream = StreamSupport.intStream(new Stepper.WideningByteStepper(steppize(cc).stepper), false)
96+
}
97+
implicit class EnrichBoxedByteKeySteppableWithSeqKeyStream[CC](cc: CC)(implicit steppize: CC => MakesKeyStepper[AnyStepper[Byte]]) {
98+
def seqKeyStream: IntStream = StreamSupport.intStream(new Stepper.WideningByteStepper(steppize(cc).keyStepper), false)
99+
}
100+
implicit class EnrichBoxedByteValueSteppableWithSeqValueStream[CC](cc: CC)(implicit steppize: CC => MakesValueStepper[AnyStepper[Byte]]) {
101+
def seqValueStream: IntStream = StreamSupport.intStream(new Stepper.WideningByteStepper(steppize(cc).valueStepper), false)
102+
}
103+
implicit class EnrichBoxedShortSteppableWithSeqStream[CC](cc: CC)(implicit steppize: CC => MakesStepper[AnyStepper[Short]])
104+
extends MakesSequentialStream[java.lang.Integer, IntStream] {
105+
def seqStream: IntStream = StreamSupport.intStream(new Stepper.WideningShortStepper(steppize(cc).stepper), false)
106+
}
107+
implicit class EnrichBoxedShortKeySteppableWithSeqKeyStream[CC](cc: CC)(implicit steppize: CC => MakesKeyStepper[AnyStepper[Short]]) {
108+
def seqKeyStream: IntStream = StreamSupport.intStream(new Stepper.WideningShortStepper(steppize(cc).keyStepper), false)
109+
}
110+
implicit class EnrichBoxedShortValueSteppableWithSeqValueStream[CC](cc: CC)(implicit steppize: CC => MakesValueStepper[AnyStepper[Short]]) {
111+
def seqValueStream: IntStream = StreamSupport.intStream(new Stepper.WideningShortStepper(steppize(cc).valueStepper), false)
112+
}
113+
implicit class EnrichBoxedCharSteppableWithSeqStream[CC](cc: CC)(implicit steppize: CC => MakesStepper[AnyStepper[Char]])
114+
extends MakesSequentialStream[java.lang.Integer, IntStream] {
115+
def seqStream: IntStream = StreamSupport.intStream(new Stepper.WideningCharStepper(steppize(cc).stepper), false)
116+
}
117+
implicit class EnrichBoxedCharKeySteppableWithSeqKeyStream[CC](cc: CC)(implicit steppize: CC => MakesKeyStepper[AnyStepper[Char]]) {
118+
def seqKeyStream: IntStream = StreamSupport.intStream(new Stepper.WideningCharStepper(steppize(cc).keyStepper), false)
119+
}
120+
implicit class EnrichBoxedCharValueSteppableWithSeqValueStream[CC](cc: CC)(implicit steppize: CC => MakesValueStepper[AnyStepper[Char]]) {
121+
def seqValueStream: IntStream = StreamSupport.intStream(new Stepper.WideningCharStepper(steppize(cc).valueStepper), false)
122+
}
123+
}
124+
41125
trait Priority2StreamConverters extends Priority3StreamConverters {
42126
implicit class EnrichDoubleSteppableWithParStream[CC](cc: CC)(implicit steppize: CC => MakesStepper[DoubleStepper with EfficientSubstep])
43127
extends MakesParallelStream[java.lang.Double, DoubleStream] {

src/main/scala/scala/compat/java8/collectionImpl/Stepper.scala

+41-4
Original file line numberDiff line numberDiff line change
@@ -239,24 +239,24 @@ trait AnyStepper[A] extends Stepper[A] with java.util.Iterator[A] with Spliterat
239239
def parStream: java.util.stream.Stream[A] = java.util.stream.StreamSupport.stream(this, true)
240240
}
241241

242-
object AnyStepper {
243-
private[collectionImpl] class BoxedDoubleStepper(st: DoubleStepper) extends AnyStepper[Double] {
242+
private[collectionImpl] object AnyStepper {
243+
final class BoxedDoubleStepper(st: DoubleStepper) extends AnyStepper[Double] {
244244
def hasNext(): Boolean = st.hasNext()
245245
def next(): Double = st.next()
246246
def characteristics(): Int = st.characteristics()
247247
def estimateSize(): Long = st.estimateSize()
248248
def substep(): AnyStepper[Double] = new BoxedDoubleStepper(st.substep())
249249
}
250250

251-
private[collectionImpl] class BoxedIntStepper(st: IntStepper) extends AnyStepper[Int] {
251+
final class BoxedIntStepper(st: IntStepper) extends AnyStepper[Int] {
252252
def hasNext(): Boolean = st.hasNext()
253253
def next(): Int = st.next()
254254
def characteristics(): Int = st.characteristics()
255255
def estimateSize(): Long = st.estimateSize()
256256
def substep(): AnyStepper[Int] = new BoxedIntStepper(st.substep())
257257
}
258258

259-
private[collectionImpl] class BoxedLongStepper(st: LongStepper) extends AnyStepper[Long] {
259+
final class BoxedLongStepper(st: LongStepper) extends AnyStepper[Long] {
260260
def hasNext(): Boolean = st.hasNext()
261261
def next(): Long = st.next()
262262
def characteristics(): Int = st.characteristics()
@@ -564,4 +564,41 @@ object Stepper {
564564
case _ => new OfLongSpliterator(sp)
565565
}
566566

567+
/* These adapter classes can wrap an AnyStepper of a small numeric type into the appropriately widened
568+
* primitive Stepper type. This provides a basis for more efficient stream processing on unboxed values
569+
* provided that the original source of the data is already boxed. In other cases the widening conversion
570+
* should always be performed directly on the original unboxed values in a custom Stepper implementation
571+
* (see for example StepsWidenedByteArray). */
572+
573+
private[java8] class WideningByteStepper(st: AnyStepper[Byte]) extends IntStepper {
574+
def hasNext(): Boolean = st.hasNext()
575+
def nextInt(): Int = st.next()
576+
def characteristics(): Int = st.characteristics() | NonNull
577+
def estimateSize(): Long = st.estimateSize()
578+
def substep(): IntStepper = new WideningByteStepper(st.substep())
579+
}
580+
581+
private[java8] class WideningCharStepper(st: AnyStepper[Char]) extends IntStepper {
582+
def hasNext(): Boolean = st.hasNext()
583+
def nextInt(): Int = st.next()
584+
def characteristics(): Int = st.characteristics() | NonNull
585+
def estimateSize(): Long = st.estimateSize()
586+
def substep(): IntStepper = new WideningCharStepper(st.substep())
587+
}
588+
589+
private[java8] class WideningShortStepper(st: AnyStepper[Short]) extends IntStepper {
590+
def hasNext(): Boolean = st.hasNext()
591+
def nextInt(): Int = st.next()
592+
def characteristics(): Int = st.characteristics() | NonNull
593+
def estimateSize(): Long = st.estimateSize()
594+
def substep(): IntStepper = new WideningShortStepper(st.substep())
595+
}
596+
597+
private[java8] class WideningFloatStepper(st: AnyStepper[Float]) extends DoubleStepper {
598+
def hasNext(): Boolean = st.hasNext()
599+
def nextDouble(): Double = st.next()
600+
def characteristics(): Int = st.characteristics() | NonNull
601+
def estimateSize(): Long = st.estimateSize()
602+
def substep(): DoubleStepper = new WideningFloatStepper(st.substep())
603+
}
567604
}

0 commit comments

Comments
 (0)