-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add an immutable array type #5885
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
Changes from all commits
a64c7cd
408cec1
da2c0de
f1645d4
44f6142
0dcb5e8
a0104fd
ea4ba34
36e3b0f
c2a3ccd
6bf73a9
c532d3e
b9decdc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
package scala | ||
import reflect.ClassTag | ||
|
||
/** An immutable array. An `IArray[T]` has the same representation as an `Array[T]`, | ||
* but it cannot be updated. Unlike regular arrays, immutable arrays are covariant. | ||
*/ | ||
opaque type IArray[+T] = Array[_ <: T] | ||
|
||
object IArray { | ||
|
||
/** Defines extension methods for immutable arrays */ | ||
implied arrayOps { | ||
|
||
/** The selection operation on an immutable array. | ||
* | ||
* @param arr the immutable array | ||
* @param n the index of the element to select | ||
* @return the element of the array at the given index | ||
*/ | ||
inline def (arr: IArray[T]) apply[T] (n: Int): T = arr.asInstanceOf[Array[T]].apply(n) | ||
|
||
/** The number of elements in an immutable array | ||
* @param arr the immutable array | ||
*/ | ||
inline def (arr: IArray[T]) length[T] : Int = arr.asInstanceOf[Array[T]].length | ||
} | ||
|
||
/** An immutable array of length 0. | ||
*/ | ||
def empty[T: ClassTag]: IArray[T] = new Array[T](0) | ||
|
||
/** An immutable array with given elements. | ||
*/ | ||
def apply[T: ClassTag](xs: T*): IArray[T] = Array(xs: _*) | ||
def apply(x: Boolean, xs: Boolean*): IArray[Boolean] = Array(x, xs: _*) | ||
def apply(x: Byte, xs: Byte*): IArray[Byte] = Array(x, xs: _*) | ||
def apply(x: Short, xs: Short*): IArray[Short] = Array(x, xs: _*) | ||
def apply(x: Char, xs: Char*): IArray[Char] = Array(x, xs: _*) | ||
def apply(x: Int, xs: Int*): IArray[Int] = Array(x, xs: _*) | ||
def apply(x: Long, xs: Long*): IArray[Long] = Array(x, xs: _*) | ||
def apply(x: Float, xs: Float*): IArray[Float] = Array(x, xs: _*) | ||
def apply(x: Double, xs: Double*): IArray[Double] = Array(x, xs: _*) | ||
def apply(x: Unit, xs: Unit*): IArray[Unit] = Array(x, xs: _*) | ||
|
||
/** Concatenates all arrays into a single immutable array. | ||
* | ||
* @param xss the given immutable arrays | ||
* @return the array created from concatenating `xss` | ||
*/ | ||
def concat[T: ClassTag](xss: IArray[T]*): IArray[T] = 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. | ||
* | ||
* @param n the number of elements in the array | ||
* @param elem the element computation | ||
*/ | ||
def fill[T: ClassTag](n: Int)(elem: => T): IArray[T] = | ||
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. | ||
* | ||
* @param n1 the number of elements in the 1st dimension | ||
* @param n2 the number of elements in the 2nd dimension | ||
* @param elem the element computation | ||
*/ | ||
def fill[T: ClassTag](n1: Int, n2: Int)(elem: => T): IArray[IArray[T]] = | ||
Array.fill(n1, n2)(elem) | ||
|
||
/** Returns a three-dimensional immutable array that contains the results of some element computation a number | ||
* of times. Each element is determined by a separate computation. | ||
* | ||
* @param n1 the number of elements in the 1st dimension | ||
* @param n2 the number of elements in the 2nd dimension | ||
* @param n3 the number of elements in the 3nd dimension | ||
* @param elem the element computation | ||
*/ | ||
def fill[T: ClassTag](n1: Int, n2: Int, n3: Int)(elem: => T): IArray[IArray[IArray[T]]] = | ||
Array.fill(n1, n2, n3)(elem) | ||
|
||
/** Returns a four-dimensional immutable array that contains the results of some element computation a number | ||
* of times. Each element is determined by a separate computation. | ||
* | ||
* @param n1 the number of elements in the 1st dimension | ||
* @param n2 the number of elements in the 2nd dimension | ||
* @param n3 the number of elements in the 3nd dimension | ||
* @param n4 the number of elements in the 4th dimension | ||
* @param elem the element computation | ||
*/ | ||
def fill[T: ClassTag](n1: Int, n2: Int, n3: Int, n4: Int)(elem: => T): IArray[IArray[IArray[IArray[T]]]] = | ||
Array.fill(n1, n2, n3, n4)(elem) | ||
|
||
/** Returns a five-dimensional immutable array that contains the results of some element computation a number | ||
* of times. Each element is determined by a separate computation. | ||
* | ||
* @param n1 the number of elements in the 1st dimension | ||
* @param n2 the number of elements in the 2nd dimension | ||
* @param n3 the number of elements in the 3nd dimension | ||
* @param n4 the number of elements in the 4th dimension | ||
* @param n5 the number of elements in the 5th dimension | ||
* @param elem the element computation | ||
*/ | ||
def fill[T: ClassTag](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(elem: => T): IArray[IArray[IArray[IArray[IArray[T]]]]] = | ||
Array.fill(n1, n2, n3, n4, n5)(elem) | ||
|
||
/** Returns an immutable array containing values of a given function over a range of integer | ||
* values starting from 0. | ||
* | ||
* @param n The number of elements in the array | ||
* @param f The function computing element values | ||
*/ | ||
def tabulate[T: ClassTag](n: Int)(f: Int => T): IArray[T] = | ||
Array.tabulate(n)(f) | ||
|
||
/** Returns a two-dimensional immutable array containing values of a given function | ||
* over ranges of integer values starting from `0`. | ||
* | ||
* @param n1 the number of elements in the 1st dimension | ||
* @param n2 the number of elements in the 2nd dimension | ||
* @param f The function computing element values | ||
*/ | ||
def tabulate[T: ClassTag](n1: Int, n2: Int)(f: (Int, Int) => T): IArray[IArray[T]] = | ||
Array.tabulate(n1, n2)(f) | ||
|
||
/** Returns a three-dimensional immutable array containing values of a given function | ||
* over ranges of integer values starting from `0`. | ||
* | ||
* @param n1 the number of elements in the 1st dimension | ||
* @param n2 the number of elements in the 2nd dimension | ||
* @param n3 the number of elements in the 3rd dimension | ||
* @param f The function computing element values | ||
*/ | ||
def tabulate[T: ClassTag](n1: Int, n2: Int, n3: Int)(f: (Int, Int, Int) => T): IArray[IArray[IArray[T]]] = | ||
Array.tabulate(n1, n2, n3)(f) | ||
|
||
/** Returns a four-dimensional immutable array containing values of a given function | ||
* over ranges of integer values starting from `0`. | ||
* | ||
* @param n1 the number of elements in the 1st dimension | ||
* @param n2 the number of elements in the 2nd dimension | ||
* @param n3 the number of elements in the 3rd dimension | ||
* @param n4 the number of elements in the 4th dimension | ||
* @param f The function computing element values | ||
*/ | ||
def tabulate[T: ClassTag](n1: Int, n2: Int, n3: Int, n4: Int)(f: (Int, Int, Int, Int) => T): IArray[IArray[IArray[IArray[T]]]] = | ||
Array.tabulate(n1, n2, n3, n4)(f) | ||
|
||
/** Returns a five-dimensional immutable array containing values of a given function | ||
* over ranges of integer values starting from `0`. | ||
* | ||
* @param n1 the number of elements in the 1st dimension | ||
* @param n2 the number of elements in the 2nd dimension | ||
* @param n3 the number of elements in the 3rd dimension | ||
* @param n4 the number of elements in the 4th dimension | ||
* @param n5 the number of elements in the 5th dimension | ||
* @param f The function computing element values | ||
*/ | ||
def tabulate[T: ClassTag](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(f: (Int, Int, Int, Int, Int) => T): IArray[IArray[IArray[IArray[IArray[T]]]]] = | ||
Array.tabulate(n1, n2, n3, n4, n5)(f) | ||
|
||
/** Returns an immutable array containing a sequence of increasing integers in a range. | ||
* | ||
* @param start the start value of the array | ||
* @param end the end value of the array, exclusive (in other words, this is the first value '''not''' returned) | ||
* @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) | ||
|
||
/** Returns an immutable array containing equally spaced values in some integer interval. | ||
* | ||
* @param start the start value of the array | ||
* @param end the end value of the array, exclusive (in other words, this is the first value '''not''' returned) | ||
* @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) | ||
|
||
/** Returns an immutable array containing repeated applications of a function to a start value. | ||
* | ||
* @param start the start value of the array | ||
* @param len the number of elements returned by the array | ||
* @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) | ||
|
||
/** Returns a decomposition of the array into a sequence. This supports | ||
* a pattern match like `{ case IArray(x,y,z) => println('3 elements')}`. | ||
* | ||
* @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]]) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
class Test[T] { | ||
type U <: T | ||
|
||
type Foo[T] = Array[T] | ||
|
||
new T // error: not a class type | ||
new T() // error: not a class type | ||
new U // error: not a class type | ||
new U() // error: not a class type | ||
new IArray[String] // error: not a class type | ||
new IArray[String]() // error: not a class type | ||
new IArray[String](10) // error: not a class type // error: too mamy arguments | ||
|
||
new Foo[String](10) // ok | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
object Test { | ||
|
||
// Can't allocate an IArray | ||
new IArray[String](10) // error: not a class type // error: too many arguments | ||
|
||
val xs = IArray(1, 2, 3) | ||
|
||
// Can't have a wildcard IArray | ||
val ys: IArray[_] = xs | ||
|
||
// Can't update an IArray | ||
xs(0) = 1 // error: value update is not a member | ||
xs(1) += 1 // error: value += is not a member | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
object Test { | ||
|
||
// Can't allocate an IArray | ||
|
||
val xs = IArray(1, 2, 3) | ||
|
||
// Can't have a wildcard IArray | ||
val ys: IArray[_] = xs // error: unreducible application | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
class x0[x1[]] // error | ||
extends x1[ // error | ||
extends x1[ | ||
// error |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import annotation.unchecked.uncheckedVariance | ||
|
||
opaque type O[+T] = Array[T @uncheckedVariance] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import reflect.ClassTag | ||
object Test extends App { | ||
|
||
val xs = IArray(1, 2, 3) | ||
|
||
def f[T](ys: IArray[T]) = { | ||
assert(ys.length == 3) | ||
var sum = 0 | ||
for (i <- 0 until ys.length) | ||
sum += xs(i) | ||
assert(sum == 6) | ||
} | ||
|
||
f(xs) | ||
|
||
var sum = 0 | ||
for (i <- 0 until xs.length) | ||
sum += xs(i) | ||
assert(sum == 6) | ||
|
||
def reduce[T](xs: IArray[T], z: T, op: (T, T) => T) = { | ||
var acc = z | ||
for (i <- 0 until xs.length) | ||
acc = op(acc, xs(i)) | ||
acc | ||
} | ||
|
||
def reduce2[T <: AnyRef](xs: IArray[T], z: T, op: (T, T) => T) = { | ||
var acc = z | ||
for (i <- 0 until xs.length) | ||
acc = op(acc, xs(i)) | ||
acc | ||
} | ||
|
||
def flatten[T: ClassTag](ys: IArray[IArray[T]]) = { | ||
var len = 0 | ||
for (i <- 0 until ys.length) len += ys(i).length | ||
val flat = new Array[T](len) | ||
var k = 0 | ||
for (i <- 0 until ys.length) { | ||
for (j <- 0 until ys(i).length) { | ||
flat(k) = ys(i)(j) | ||
k += 1 | ||
} | ||
} | ||
IArray(flat: _*) | ||
} | ||
|
||
val ys = IArray.concat(xs, xs, xs) | ||
assert(reduce(ys, 0, _ + _) == 18) | ||
|
||
val ss = IArray("a", "b", "c") | ||
assert(reduce2(ss, "", _ ++ _) == "abc") | ||
|
||
val zss = IArray.fill(2, 3)(1) | ||
val zs = flatten(zss) | ||
assert(reduce(zs, 0, _ + _) == 6) | ||
|
||
val is = IArray.iterate(0, 4)(_ + 1) | ||
assert(reduce(is, 0, _ + _) == 6) | ||
|
||
val IArray(1, 2, 3) = xs | ||
|
||
val as: IArray[Any] = IArray(1, "hello") | ||
assert(as(as.length - 1) == "hello") | ||
assert(reduce(as, 0, (x, y) => x.toString ++ y.toString) == "01hello") | ||
|
||
// Check that representation of IArray and Array is the same | ||
val bs: IArray[Double] = IArray(1.0, 2.0) | ||
val cs: Array[Double] = bs.asInstanceOf[Array[Double]] | ||
cs(1) = 3.0 | ||
assert(bs(1) == 3.0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we also check that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's harder. I believe there are ways to test it, but I don't know them. The fact that |
||
} |
Uh oh!
There was an error while loading. Please reload this page.
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 type does this method erase to?
f(ys: Array[AnyRef])
? Orf(ys: AnyRef)
?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.
No, it's
Object
. Just whatArray[T]
erases to.