diff --git a/library/src/scala/IArray.scala b/library/src/scala/IArray.scala index f1abbb213644..6207c3f6d17d 100644 --- a/library/src/scala/IArray.scala +++ b/library/src/scala/IArray.scala @@ -7,6 +7,9 @@ import reflect.ClassTag object opaques: opaque type IArray[+T] = Array[_ <: T] + private[scala] type Sub[A] >: Array[A] <: IArray[A] + private[scala] type Sup[A] >: IArray[A] <: Array[_ <: A] + /** Defines extension methods for immutable arrays */ given arrayOps: Object { @@ -41,11 +44,13 @@ object opaques: /** Returns this array concatenated with the given array. */ def [T, U >: T: ClassTag](arr: IArray[T]) ++(that: IArray[U]): IArray[U] = - (genericArrayOps(arr) ++ that.asInstanceOf[Array[U]]).asInstanceOf[IArray[U]] + genericArrayOps(arr) ++ that /** Tests whether this array contains a given value as an element. */ def [T](arr: IArray[T]) contains(elem: T): Boolean = - genericArrayOps(arr.asInstanceOf[Array[T]]).contains(elem) + // `genericArrayOps(arr).contains(elem)` does not work because `elem` does not have type `arr.T` + // but we can use `exists` instead, which is how `ArrayOps#contains` itself is implemented: + genericArrayOps(arr).exists(_ == elem) /** Counts the number of elements in this array which satisfy a predicate */ def [T](arr: IArray[T]) count(p: T => Boolean): Int = @@ -53,27 +58,27 @@ object opaques: /** The rest of the array without its `n` first elements. */ def [T](arr: IArray[T]) drop(n: Int): IArray[T] = - genericArrayOps(arr).drop(n).asInstanceOf[IArray[T]] + genericArrayOps(arr).drop(n) /** The rest of the array without its `n` last elements. */ def [T](arr: IArray[T]) dropRight(n: Int): IArray[T] = - genericArrayOps(arr).dropRight(n).asInstanceOf[IArray[T]] + genericArrayOps(arr).dropRight(n) /** Drops longest prefix of elements that satisfy a predicate. */ def [T](arr: IArray[T]) dropWhile(p: T => Boolean): IArray[T] = - genericArrayOps(arr).dropWhile(p).asInstanceOf[IArray[T]] + genericArrayOps(arr).dropWhile(p) /** Tests whether a predicate holds for at least one element of this array. */ - def [T](arr: IArray[T]) exists(p: T => Boolean): IArray[T] = - genericArrayOps(arr).exists(p).asInstanceOf[IArray[T]] + def [T](arr: IArray[T]) exists(p: T => Boolean): Boolean = + genericArrayOps(arr).exists(p) /** Selects all elements of this array which satisfy a predicate. */ def [T](arr: IArray[T]) filter(p: T => Boolean): IArray[T] = - genericArrayOps(arr).filter(p).asInstanceOf[IArray[T]] + genericArrayOps(arr).filter(p) /** Selects all elements of this array which do not satisfy a predicate. */ def [T](arr: IArray[T]) filterNot(p: T => Boolean): IArray[T] = - genericArrayOps(arr).filterNot(p).asInstanceOf[IArray[T]] + genericArrayOps(arr).filterNot(p) /** Finds the first element of the array satisfying a predicate, if any. */ def [T](arr: IArray[T]) find(p: T => Boolean): Option[T] = @@ -82,12 +87,12 @@ object opaques: /** Builds a new array by applying a function to all elements of this array * and using the elements of the resulting collections. */ def [T, U: ClassTag](arr: IArray[T]) flatMap(f: T => IterableOnce[U]): IArray[U] = - genericArrayOps(arr).flatMap(f).asInstanceOf[IArray[U]] + genericArrayOps(arr).flatMap(f) /** Flattens a two-dimensional array by concatenating all its rows * into a single array. */ def [T, U: ClassTag](arr: IArray[T]) flatten(using T => Iterable[U]): IArray[U] = - genericArrayOps(arr).flatten.asInstanceOf[IArray[U]] + genericArrayOps(arr).flatten /** Folds the elements of this array using the specified associative binary operator. */ def [T, U >: T: ClassTag](arr: IArray[T]) fold(z: U)(op: (U, U) => U): U = @@ -121,7 +126,10 @@ object opaques: /** Finds index of first occurrence of some value in this array after or at some start index. */ def [T](arr: IArray[T]) indexOf(elem: T, from: Int = 0): Int = - genericArrayOps(arr.asInstanceOf[Array[T]]).indexOf(elem, from) + // `asInstanceOf` needed because `elem` does not have type `arr.T` + // We could use `arr.iterator.indexOf(elem, from)` or `arr.indexWhere(_ == elem, from)` + // but these would incur some overhead. + genericArrayOps(arr).indexOf(elem.asInstanceOf, from) /** Finds index of the first element satisfying some predicate after or at some start index. */ def [T](arr: IArray[T]) indexWhere(p: T => Boolean, from: Int = 0): Int = @@ -133,7 +141,7 @@ object opaques: /** The initial part of the array without its last element. */ def [T](arr: IArray[T]) init: IArray[T] = - genericArrayOps(arr).init.asInstanceOf[IArray[T]] + genericArrayOps(arr).init /** Tests whether the array is empty. */ def [T](arr: IArray[T]) isEmpty: Boolean = @@ -153,7 +161,8 @@ object opaques: /** Finds index of last occurrence of some value in this array before or at a given end index. */ def [T](arr: IArray[T]) lastIndexOf(elem: T, end: Int = arr.length - 1): Int = - genericArrayOps(arr.asInstanceOf[Array[T]]).lastIndexOf(elem, end) + // see: same issue in `indexOf` + genericArrayOps(arr).lastIndexOf(elem.asInstanceOf, end) /** Finds index of last element satisfying some predicate before or at given end index. */ def [T](arr: IArray[T]) lastIndexWhere(p: T => Boolean, end: Int = arr.length - 1): Int = @@ -161,7 +170,7 @@ object opaques: /** Builds a new array by applying a function to all elements of this array. */ def [T, U: ClassTag](arr: IArray[T]) map(f: T => U): IArray[U] = - genericArrayOps(arr).map(f).asInstanceOf[IArray[U]] + genericArrayOps(arr).map(f) /** Tests whether the array is not empty. */ def [T](arr: IArray[T]) nonEmpty: Boolean = @@ -169,27 +178,25 @@ object opaques: /** A pair of, first, all elements that satisfy predicate `p` and, second, all elements that do not. */ def [T](arr: IArray[T]) partition(p: T => Boolean): (IArray[T], IArray[T]) = - genericArrayOps(arr).partition(p) match { - case (x, y) => (x.asInstanceOf[IArray[T]], y.asInstanceOf[IArray[T]]) - } + genericArrayOps(arr).partition(p) /** Returns a new array with the elements in reversed order. */ def [T](arr: IArray[T]) reverse: IArray[T] = - genericArrayOps(arr).reverse.asInstanceOf[IArray[T]] + genericArrayOps(arr).reverse /** Computes a prefix scan of the elements of the array. */ def [T, U >: T: ClassTag](arr: IArray[T]) scan(z: U)(op: (U, U) => U): IArray[U] = - genericArrayOps(arr).scan(z)(op).asInstanceOf[IArray[U]] + genericArrayOps(arr).scan(z)(op) /** Produces an array containing cumulative results of applying the binary * operator going left to right. */ def [T, U: ClassTag](arr: IArray[T]) scanLeft(z: U)(op: (U, T) => U): IArray[U] = - genericArrayOps(arr).scanLeft(z)(op).asInstanceOf[IArray[U]] + genericArrayOps(arr).scanLeft(z)(op) /** Produces an array containing cumulative results of applying the binary * operator going right to left. */ def [T, U: ClassTag](arr: IArray[T]) scanRight(z: U)(op: (T, U) => U): IArray[U] = - genericArrayOps(arr).scanRight(z)(op).asInstanceOf[IArray[U]] + genericArrayOps(arr).scanRight(z)(op) /** The size of this array. */ def [T](arr: IArray[T]) size: Int = @@ -197,92 +204,91 @@ object opaques: /** Selects the interval of elements between the given indices. */ def [T](arr: IArray[T]) slice(from: Int, until: Int): IArray[T] = - genericArrayOps(arr).slice(from, until).asInstanceOf[IArray[T]] + genericArrayOps(arr).slice(from, until) /** Sorts this array according to the Ordering which results from transforming * an implicitly given Ordering with a transformation function. */ def [T, U: ClassTag](arr: IArray[T]) sortBy(f: T => U)(using math.Ordering[U]): IArray[T] = - genericArrayOps(arr).sortBy(f).asInstanceOf[IArray[T]] + genericArrayOps(arr).sortBy(f) /** Sorts this array according to a comparison function. */ def [T](arr: IArray[T]) sortWith(f: (T, T) => Boolean): IArray[T] = - genericArrayOps(arr).sortWith(f).asInstanceOf[IArray[T]] + genericArrayOps(arr).sortWith(f) /** Sorts this array according to an Ordering. */ def [T](arr: IArray[T]) sorted(using math.Ordering[T]): IArray[T] = - genericArrayOps(arr).sorted.asInstanceOf[IArray[T]] + genericArrayOps(arr).sorted /** Splits this array into a prefix/suffix pair according to a predicate. */ def [T](arr: IArray[T]) span(p: T => Boolean): (IArray[T], IArray[T]) = - genericArrayOps(arr).span(p) match { - case (x, y) => (x.asInstanceOf[IArray[T]], y.asInstanceOf[IArray[T]]) - } + genericArrayOps(arr).span(p) /** Splits this array into two at a given position. */ def [T](arr: IArray[T]) splitAt(n: Int): (IArray[T], IArray[T]) = - genericArrayOps(arr).splitAt(n) match { - case (x, y) => (x.asInstanceOf[IArray[T]], y.asInstanceOf[IArray[T]]) - } + genericArrayOps(arr).splitAt(n) /** Tests whether this array starts with the given array. */ def [T, U >: T: ClassTag](arr: IArray[T]) startsWith(that: IArray[U], offset: Int = 0): Boolean = - genericArrayOps(arr).startsWith(that.asInstanceOf[Array[U]]) + genericArrayOps(arr).startsWith(that) /** The rest of the array without its first element. */ def [T](arr: IArray[T]) tail: IArray[T] = - genericArrayOps(arr).tail.asInstanceOf[IArray[T]] + genericArrayOps(arr).tail /** An array containing the first `n` elements of this array. */ def [T](arr: IArray[T]) take(n: Int): IArray[T] = - genericArrayOps(arr).take(n).asInstanceOf[IArray[T]] + genericArrayOps(arr).take(n) /** An array containing the last `n` elements of this array. */ def [T](arr: IArray[T]) takeRight(n: Int): IArray[T] = - genericArrayOps(arr).takeRight(n).asInstanceOf[IArray[T]] + genericArrayOps(arr).takeRight(n) /** Takes longest prefix of elements that satisfy a predicate. */ def [T](arr: IArray[T]) takeWhile(p: T => Boolean): IArray[T] = - genericArrayOps(arr).takeWhile(p).asInstanceOf[IArray[T]] + genericArrayOps(arr).takeWhile(p) /** Converts an array of pairs into an array of first elements and an array of second elements. */ def [U: ClassTag, V: ClassTag](arr: IArray[(U, V)]) unzip: (IArray[U], IArray[V]) = - genericArrayOps(arr).unzip match { - case (x, y) => (x.asInstanceOf[IArray[U]], y.asInstanceOf[IArray[V]]) - } + genericArrayOps(arr).unzip /** Returns an array formed from this array and another iterable collection * by combining corresponding elements in pairs. * If one of the two collections is longer than the other, its remaining elements are ignored. */ def [T, U: ClassTag](arr: IArray[T]) zip(that: IArray[U]): IArray[(T, U)] = - genericArrayOps(arr).zip(that).asInstanceOf[IArray[(T, U)]] + genericArrayOps(arr).zip(that) } end opaques type IArray[+T] = opaques.IArray[T] object IArray { + import opaques.Sub + import opaques.Sup + + // A convenience to avoid having to cast everything by hand + private given [A] as Conversion[Array[A], IArray[A]] = identity[Sub[A]] /** An immutable array of length 0. */ - def empty[T: ClassTag]: IArray[T] = new Array[T](0).asInstanceOf + def empty[T: ClassTag]: IArray[T] = new Array[T](0) /** An immutable boolean array of length 0. */ - def emptyBooleanIArray = Array.emptyBooleanArray.asInstanceOf[IArray[Boolean]] + def emptyBooleanIArray: IArray[Boolean] = Array.emptyBooleanArray /** An immutable byte array of length 0. */ - def emptyByteIArray = Array.emptyByteArray.asInstanceOf[IArray[Byte]] + def emptyByteIArray: IArray[Byte] = Array.emptyByteArray /** An immutable char array of length 0. */ - def emptyCharIArray = Array.emptyCharArray.asInstanceOf[IArray[Char]] + def emptyCharIArray: IArray[Char] = Array.emptyCharArray /** An immutable double array of length 0. */ - def emptyDoubleIArray = Array.emptyDoubleArray.asInstanceOf[IArray[Double]] + def emptyDoubleIArray: IArray[Double] = Array.emptyDoubleArray /** An immutable float array of length 0. */ - def emptyFloatIArray = Array.emptyFloatArray.asInstanceOf[IArray[Float]] + def emptyFloatIArray: IArray[Float] = Array.emptyFloatArray /** An immutable int array of length 0. */ - def emptyIntIArray = Array.emptyIntArray.asInstanceOf[IArray[Int]] + def emptyIntIArray: IArray[Int] = Array.emptyIntArray /** An immutable long array of length 0. */ - def emptyLongIArray = Array.emptyLongArray.asInstanceOf[IArray[Long]] + def emptyLongIArray: IArray[Long] = Array.emptyLongArray /** An immutable short array of length 0. */ - def emptyShortIArray = Array.emptyShortArray.asInstanceOf[IArray[Short]] + def emptyShortIArray: IArray[Short] = Array.emptyShortArray /** An immutable object array of length 0. */ - def emptyObjectIArray = Array.emptyObjectArray.asInstanceOf[IArray[Object]] + def emptyObjectIArray: IArray[Object] = Array.emptyObjectArray /** An immutable array with given elements. */ inline def apply[T](inline xs: T*)(using inline ct: ClassTag[T]): IArray[T] = Array(xs: _*).asInstanceOf @@ -311,7 +317,10 @@ object IArray { * @return the array created from concatenating `xss` */ def concat[T: ClassTag](xss: IArray[T]*): IArray[T] = - Array.concat[T](xss.asInstanceOf[Seq[Array[T]]]: _*).asInstanceOf + // `Array.concat` should arguably take in a `Seq[Array[_ <: T]]`, + // but since it currently takes a `Seq[Array[T]]` we have to perform a cast, + // knowing tacitly that `concat` is not going to do the wrong thing. + Array.concat[T](xss.asInstanceOf[Seq[Array[T]]]: _*) /** Returns an immutable array that contains the results of some element computation a number * of times. Each element is determined by a separate computation. @@ -320,7 +329,7 @@ object IArray { * @param elem the element computation */ def fill[T: ClassTag](n: Int)(elem: => T): IArray[T] = - Array.fill(n)(elem).asInstanceOf + Array.fill(n)(elem) /** Returns a two-dimensional immutable array that contains the results of some element computation a number * of times. Each element is determined by a separate computation. @@ -330,6 +339,7 @@ object IArray { * @param elem the element computation */ def fill[T: ClassTag](n1: Int, n2: Int)(elem: => T): IArray[IArray[T]] = + // We cannot avoid a cast here as Array.fill creates inner arrays out of our control: Array.fill(n1, n2)(elem).asInstanceOf /** Returns a three-dimensional immutable array that contains the results of some element computation a number @@ -375,7 +385,7 @@ object IArray { * @param f The function computing element values */ def tabulate[T: ClassTag](n: Int)(f: Int => T): IArray[T] = - Array.tabulate(n)(f).asInstanceOf + Array.tabulate(n)(f) /** Returns a two-dimensional immutable array containing values of a given function * over ranges of integer values starting from `0`. @@ -430,7 +440,7 @@ object IArray { * @return the immutable array with values in range `start, start + 1, ..., end - 1` * up to, but excluding, `end`. */ - def range(start: Int, end: Int): IArray[Int] = Array.range(start, end).asInstanceOf + def range(start: Int, end: Int): IArray[Int] = Array.range(start, end) /** Returns an immutable array containing equally spaced values in some integer interval. * @@ -439,7 +449,7 @@ object IArray { * @param step the increment value of the array (may not be zero) * @return the immutable array with values in `start, start + step, ...` up to, but excluding `end` */ - def range(start: Int, end: Int, step: Int): IArray[Int] = Array.range(start, end, step).asInstanceOf + def range(start: Int, end: Int, step: Int): IArray[Int] = Array.range(start, end, step) /** Returns an immutable array containing repeated applications of a function to a start value. * @@ -448,7 +458,7 @@ object IArray { * @param f the function that is repeatedly applied * @return the immutable array returning `len` values in the sequence `start, f(start), f(f(start)), ...` */ - def iterate[T: ClassTag](start: T, len: Int)(f: T => T): IArray[T] = Array.iterate(start, len)(f).asInstanceOf + def iterate[T: ClassTag](start: T, len: Int)(f: T => T): IArray[T] = Array.iterate(start, len)(f) /** Returns a decomposition of the array into a sequence. This supports * a pattern match like `{ case IArray(x,y,z) => println('3 elements')}`. @@ -456,5 +466,8 @@ object IArray { * @param x the selector value * @return sequence wrapped in a [[scala.Some]], if `x` is a Seq, otherwise `None` */ - def unapplySeq[T](x: IArray[T]) = Array.unapplySeq[T](x.asInstanceOf[Array[T]]) -} \ No newline at end of file + def unapplySeq[T](x: IArray[T]) = + // The double type ascription is currently needed, + // for some reason (see: https://scastie.scala-lang.org/sSsmOhKxSKym405MgNRKqQ) + Array.unapplySeq((x: Sup[T]): Array[_ <: T]) +}