Remove Array.set_at (#3634)

Implements https://www.pivotaltracker.com/story/show/182879865

# Important Notes
Note that removing `set_at` still does not make our arrays fully immutable - `Array.copy` can still be used to mutate them.
This commit is contained in:
Radosław Waśko 2022-08-26 11:34:33 +02:00 committed by GitHub
parent 14c516aff3
commit fd318cfa96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 139 additions and 147 deletions

View File

@ -184,6 +184,7 @@
- [Short-hand syntax for `order_by` added.][3643]
- [Expanded `Table.at` to support index access and added `Table.column_count`
method.][3644]
- [Removed `Array.set_at`.][3634]
[debug-shortcuts]:
https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug
@ -290,6 +291,7 @@
[3644]: https://github.com/enso-org/enso/pull/3644
[3648]: https://github.com/enso-org/enso/pull/3648
[5250]: https://github.com/enso-org/enso/pull/5250
[3634]: https://github.com/enso-org/enso/pull/3634
#### Enso Compiler
@ -370,7 +372,7 @@
[3631]: https://github.com/enso-org/enso/pull/3631
[3633]: https://github.com/enso-org/enso/pull/3633
[3637]: https://github.com/enso-org/enso/pull/3637
[3633]: https://github.com/enso-org/enso/pull/3641
[3641]: https://github.com/enso-org/enso/pull/3641
[3658]: https://github.com/enso-org/enso/pull/3658
# Enso 2.0.0-alpha.18 (2021-10-12)

View File

@ -23,22 +23,6 @@ type Array
at : Integer -> Any
at self index = @Builtin_Method "Array.at"
## Set the cell at the specified index to the provided value, returning
the array.
Arguments:
- index: The position in the array to set.
- value: The value to set at position index.
The array is mutated in place, and only returned to facilitate a natural
programming style in Enso.
? Safety
If index < 0 or index >= self.length, then this operation will result
in an Invalid_Array_Index_Error exception.
set_at : Integer -> Any -> Array
set_at self index value = @Builtin_Method "Array.set_at"
## Gets the length of the array this.
> Example

View File

@ -5,6 +5,10 @@ from Standard.Base.Data.Index_Sub_Range import While, By_Index, Sample, Every
import Standard.Base.Polyglot.Proxy_Polyglot_Array
import Standard.Base.Random
polyglot java import java.util.ArrayList
polyglot java import java.lang.IndexOutOfBoundsException
polyglot java import org.enso.base.Array_Utils
## Creates a new vector of the given length, initializing elements using
the provided constructor function.
@ -26,9 +30,9 @@ import Standard.Base.Random
Vector.new my_vec.length (ix -> my_vec.at ix)
new : Number -> (Number -> Any) -> Vector Any
new length constructor =
arr = Array.new length
0.up_to length . each ix-> arr.set_at ix (constructor ix)
Vector arr
builder = (0.up_to length).fold (new_builder length) builder-> ix->
builder.append (constructor ix)
builder.to_vector
## Creates a new vector of the given length, filling the elements with
the provided constant.
@ -46,9 +50,9 @@ new length constructor =
Vector.fill length=50 item=42
fill : Number -> Any -> Vector Any
fill length ~item =
arr = Array.new length
0.up_to length . each ix-> arr.set_at ix item
Vector arr
builder = (0.up_to length).fold (new_builder length) builder-> _->
builder.append item
builder.to_vector
## Creates a new vector builder instance.
@ -63,18 +67,18 @@ fill length ~item =
- capacity: Initial capacity of the Vector.Builder
> Example
Construct a vector using a builder that contains the items 1 to 10.
Construct a vector using a builder that contains the items 1 to 5.
example_new_builder =
builder = Vector.new_builder 10
builder = Vector.new_builder 5
do_build start stop =
builder.append start
if start >= stop then Nothing else
@Tail_Call do_build start+1 stop
do_build 1 10
do_build 1 5
builder.to_vector
new_builder : Integer -> Builder
new_builder (capacity=1) = Builder.new capacity
new_builder (capacity=10) = Builder.new capacity
## ADVANCED
@ -113,12 +117,12 @@ type Vector
Vector.fill length=50 item=42
type Vector storage
## PRIVATE
to_array self =
arr = self.storage.to_array
case arr of
Array ->
arr
_ ->
case Meta.meta arr of
Meta.Primitive _ -> arr
_ ->
len = self.storage.length
a = Array.new len
Array.copy arr 0 a 0 len
@ -986,8 +990,7 @@ type Builder
A builder type for Enso vectors.
Arguments:
- to_array: The accumulator for the new vector.
- length: The current length of the vector being built.
- java_builder: The accumulator for the new vector.
A vector builder is a mutable data structure, that allows to gather a
number of elements and then convert them to a vector. This is
@ -1007,7 +1010,14 @@ type Builder
@Tail_Call do_build new_builder start+1 stop
builder = do_build Vector.new_builder 1 10
builder.to_vector
type Builder to_array length
! TODO
We may want to revisit the fold pattern - it is required for correct
propagation of dataflow errors, but it is very easy to forget about it
and get wrong error propagation. Instead we may want to have a `Ref`
inside of the Builder. Any error detected during `append` could set
that `Ref` and then `to_vector` could propagate that error.
type Builder java_builder
## Creates a new builder.
@ -1019,21 +1029,11 @@ type Builder
Vector.new_builder
new : Integer->Builder
new (capacity=1) = Builder (Array.new capacity) 0
## Returns the current capacity (i.e. the size of the underlying storage)
of this builder.
> Example
Get the capacity of a new builder.
Vector.new_builder.capacity
capacity : Integer
capacity self = self.to_array.length
new (capacity=10) = Builder (ArrayList.new capacity)
## Checks if this builder is empty.
is_empty : Boolean
is_empty self = self.length == 0
is_empty self = self.java_builder.isEmpty
## Checks if this builder is not empty.
not_empty : Boolean
@ -1056,6 +1056,27 @@ type Builder
self.unsafe_append item
self
## Appends a part of a given vector to this builder
Arguments:
- vector: The vector from which the elements are sourced.
- start: The start index of the range to append.
- end: The end index (the first index after the last element to be
appended) of the range to be appended.
> Example
Append a part of the vector.
builder = Vector.new_builder
builder . append_vector_range [20, 30, 40, 50] 1 3 . to_vector == [30, 40]
append_vector_range : Vector Any ! Error -> Builder ! Error
append_vector_range self vector start end =
subrange = vector.slice start end
## This workaround is needed because
`self.java_builder.addAll subrange.to_array` fails with
`Unsupported argument types: [Array]`.
Array_Utils.appendToArrayList self.java_builder subrange.to_array
## PRIVATE
Appends a new element into this builder.
@ -1075,17 +1096,7 @@ type Builder
Vector.new_builder.unsafe_append 10
unsafe_append : Any -> Nothing
unsafe_append self item = case self.capacity > self.length of
True ->
self.to_array.set_at self.length item
Unsafe.set_atom_field self 1 (self.length + 1)
False ->
old_array = self.to_array
new_array = Array.new old_array.length*2
Array.copy old_array 0 new_array 0 old_array.length
Unsafe.set_atom_field self 0 new_array
self.append item
Nothing
unsafe_append self item = self.java_builder.add item
## Gets an element from the vector at a specified index (0-based).
@ -1096,7 +1107,7 @@ type Builder
at : Integer -> Any ! Index_Out_Of_Bounds_Error
at self index =
actual_index = if index < 0 then self.length + index else index
Panic.catch Invalid_Array_Index_Error (self.to_array.at actual_index) _->
Panic.catch IndexOutOfBoundsException (self.java_builder.get actual_index) _->
Error.throw (Index_Out_Of_Bounds_Error index self.length)
## Checks whether a predicate holds for at least one element of this builder.
@ -1107,7 +1118,7 @@ type Builder
exists : (Any -> Boolean) -> Boolean
exists self predicate =
0.up_to self.length . exists (idx -> (predicate (self.to_array.at idx)))
0.up_to self.length . exists (idx -> (predicate (self.java_builder.get idx)))
## Converts this builder to a vector containing all the appended elements.
@ -1122,10 +1133,10 @@ type Builder
bldr.to_vector
to_vector : Vector Any
to_vector self =
old_array = self.to_array
new_array = Array.new self.length
Array.copy old_array 0 new_array 0 self.length
Vector new_array
## This creates a fresh copy of the builders storage, so any future
changes to the builder will not affect the returned vector.
new_array = self.java_builder.toArray
from_polyglot_array new_array
## UNSTABLE
@ -1211,21 +1222,17 @@ slice_many_ranges vector ranges =
new_length = ranges.fold 0 acc-> descriptor-> case descriptor of
Integer -> acc+1
Range _ _ _ -> acc+descriptor.length
arr = Array.new new_length
ranges.fold 0 start_ix-> descriptor-> case descriptor of
builder = new_builder new_length
ranges.each descriptor-> case descriptor of
Integer ->
arr.set_at start_ix (vector.unsafe_at descriptor)
start_ix+1
builder.append (vector.unsafe_at descriptor)
Range start end step -> case step == 1 of
True ->
len = end-start
Array.copy vector.to_array start arr start_ix len
start_ix+len
builder.append_vector_range vector start end
False ->
descriptor.each_with_index within_range_ix-> descriptor_ix->
arr.set_at start_ix+within_range_ix (vector.unsafe_at descriptor_ix)
start_ix+descriptor.length
Vector arr
descriptor.each ix->
builder.append (vector.unsafe_at ix)
builder.to_vector
## PRIVATE
Takes a list of descriptors and returns a new one where ranges with

View File

@ -18,8 +18,8 @@ core_op : Mat -> Any -> (Mat -> Scalar -> Mat -> Nothing) -> Nothing
core_op mat value function =
result = Mat.new
scalar = case value of
Vector.Vector arr ->
Scalar.new arr
Vector.Vector _ ->
Scalar.new value.to_array
Matrix.Matrix m ->
if ((m.rows == mat.rows) && (m.cols == mat.cols) && (m.channels == mat.channels)) then m else Panic.throw Matrix.Dimensions_Not_Equal
_ ->

View File

@ -346,8 +346,8 @@ Error.should_equal self _ frames_to_skip=0 = fail_match_on_unexpected_error self
example_should_equal =
1.00000001 . should_equal 1.00000002 epsilon=0.0001
Decimal.should_equal : Decimal -> Decimal -> Integer -> Assertion
Decimal.should_equal self that epsilon=0 frames_to_skip=0 =
Number.should_equal : Decimal -> Decimal -> Integer -> Assertion
Number.should_equal self that epsilon=0 frames_to_skip=0 =
matches = case that of
Number -> self.equals that epsilon
_ -> False

View File

@ -13,11 +13,11 @@ import Standard.Visualization.Helpers
json_from_table : Table.Table -> Object
json_from_table table =
names = ['label', 'latitude', 'longitude', 'radius', 'color']
pairs = names.filter_map <| name->
pairs = names.map <| name->
column = table.lookup_ignore_case name
column.when_valid ["df_" + name, column.to_vector]
column.when_valid ["df_" + name, column.to_vector] . catch Nothing
Json.from_pairs pairs
Json.from_pairs <| pairs.filter (x -> x.is_nothing.not)
## PRIVATE

View File

@ -4,16 +4,6 @@ import Standard.Table.Data.Column
import Standard.Table.Data.Storage
import Standard.Table.Data.Table
## PRIVATE
Maps the vector using the given function. Filters out all error values.
Arguments:
- f: unary invokable that is applied to each vector element. Non-error
values are returned in the resulting vector. Error values are dropped.
Vector.Vector.filter_map : Any -> Vector
Vector.Vector.filter_map self f = self.map f . filter .is_valid
## PRIVATE
Returns the given value if this is not an error. Propagates error otherwise.
@ -116,9 +106,8 @@ Table.Table.all_columns self =
- text: the case-insensitive name of the searched column.
Table.Table.lookup_ignore_case : Text -> Column ! Nothing
Table.Table.lookup_ignore_case self name =
ret = self.all_columns.find <| col->
self.all_columns.find <| col->
col.name.equals_ignore_case name
ret
## PRIVATE

View File

@ -107,8 +107,8 @@ No_Fallback_Column.to_display_text self =
Generates JSON that describes points data.
Table.Table.point_data : Table -> Object
Table.Table.point_data self =
get_point_data field = field.lookup_in self . rename field.name
columns = Point_Data.all_fields.filter_map get_point_data
get_point_data field = field.lookup_in self . rename field.name . catch Any (_->Nothing)
columns = Point_Data.all_fields.map get_point_data . filter (x -> x.is_nothing.not)
(0.up_to self.row_count).to_vector.map <| row_n->
pairs = columns.map column->
value = column.at row_n . catch_ Nothing

View File

@ -8,7 +8,6 @@ import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import org.enso.interpreter.dsl.AcceptsError;
import org.enso.interpreter.dsl.Builtin;
import org.enso.interpreter.node.expression.builtin.error.InvalidArrayIndexError;
import org.enso.interpreter.runtime.Context;
@ -111,13 +110,6 @@ public class Array implements TruffleObject {
return getItems()[(int) index];
}
@Builtin.Method(name = "setAt", description = "Gets an array element at the given index.")
@Builtin.WrapException(from = IndexOutOfBoundsException.class, to = InvalidArrayIndexError.class)
public Object set(long index, @AcceptsError Object value) {
getItems()[(int) index] = value;
return this;
}
/**
* Exposes an index validity check through the polyglot API.
*
@ -129,21 +121,6 @@ public class Array implements TruffleObject {
return index < getArraySize() && index >= 0;
}
@ExportMessage
void writeArrayElement(long index, Object value) {
items[(int) index] = value;
}
@ExportMessage
boolean isArrayElementModifiable(long index) {
return isArrayElementReadable(index);
}
@ExportMessage
boolean isArrayElementInsertable(long index) {
return false;
}
@ExportMessage
String toDisplayString(boolean b) {
return toString();

View File

@ -1,5 +1,8 @@
package org.enso.base;
import java.util.ArrayList;
import java.util.List;
public class Array_Utils {
/**
* This function forces the polyglot conversion of an Enso array into a `byte[]`. This allows for
@ -11,4 +14,9 @@ public class Array_Utils {
public static byte[] ensureByteArray(byte[] input) {
return input;
}
/** A temporary workaround to be able to efficiently append an array to `ArrayList`. */
public static <T> void appendToArrayList(ArrayList<T> builder, List<T> list) {
builder.addAll(list);
}
}

View File

@ -2,40 +2,64 @@ from Standard.Base import all
import Standard.Test
polyglot java import java.util.ArrayList
Array.method self = 0
spec = Test.group "Arrays" <|
Test.specify "should be able to be converted to a visualization rep" <|
arr = Vector.fill 1000 0 . to_array
text = arr.to_default_visualization_data
json = Json.parse text
as_vec = json.into (Vector.Vector Number)
as_vec.should_equal <| Vector.fill 100 0
## Returns an array with the same contents as the given vector, surely backed by
the Enso Array primitive.
make_enso_array vector =
enso_array = Array.new vector.length
Array.copy vector.to_array 0 enso_array 0 vector.length
enso_array
## Returns an array with the same contents as the given vector, surely backed by
a Java array.
make_java_array vector =
builder = ArrayList.new
vector.each x->
builder.add x
builder.toArray
test_arrays array_from_vector =
Test.specify "should allow accessing elements" <|
arr = [1, 2, 3] . to_array
arr = array_from_vector [1, 2, 3]
arr.at 0 . should_equal 1
arr.at 2 . should_equal 3
Test.specify "should allow setting elements" <|
arr = [1, 2, 3] . to_array
arr.set_at 1 10
arr.at 1 . should_equal 10
Vector.from_polyglot_array arr . should_equal [1, 10, 3]
Test.specify "should panic on out of bounds access" <|
arr = [1, 2, 3] . to_array
arr = array_from_vector [1, 2, 3]
Test.expect_panic_with (arr.at -1) Invalid_Array_Index_Error
Test.expect_panic_with (arr.at 3) Invalid_Array_Index_Error
Test.expect_panic_with (arr.set_at 3 100) Invalid_Array_Index_Error
Test.specify "should allow for functional dispatch on a method defined in this module"
arr = [1, 2, 3] . to_array
arr.method . should_equal 0
spec =
Test.group "Enso Arrays" <|
test_arrays make_enso_array
Test.specify "should propagate dataflow errors" <|
err = Error.throw (Illegal_State_Error "Foo")
res = Array.new err
res . should_fail_with Illegal_State_Error
Test.specify "should allow for functional dispatch on a method defined in this module" <|
arr = make_enso_array [1, 2, 3]
arr.method . should_equal 0
Test.specify "should propagate dataflow errors" <|
err = Error.throw (Illegal_State_Error "Foo")
res = Array.new err
res . should_fail_with Illegal_State_Error
Test.specify "should be able to be converted to a visualization rep" <|
arr = make_enso_array (Vector.fill 1000 0)
text = arr.to_default_visualization_data
json = Json.parse text
as_vec = json.into (Vector.Vector Number)
as_vec.should_equal <| Vector.fill 100 0
Test.group "Polyglot Arrays" <|
test_arrays make_java_array
Test.specify "should be able to be converted to a visualization rep" pending="`to_default_visualization_data` does not work for polyglot arrays" <|
arr = make_java_array (Vector.fill 1000 0)
text = arr.to_default_visualization_data
json = Json.parse text
as_vec = json.into (Vector.Vector Number)
as_vec.should_equal <| Vector.fill 100 0
main = Test.Suite.run_main spec

View File

@ -412,10 +412,10 @@ spec = Test.group "Vectors" <|
small_expected = [T 4 0, T 1 3, T 1 8, T -1 10, T -1 1, T -20 0]
small_vec.sort order=Sort_Direction.Descending . should_equal small_expected
Test.specify "should be able to map over errors" <|
fail a = Error.throw <| My_Error a
[fail 1].map (x -> x.catch Any (x -> x.a)) . should_equal [1]
[1].map fail . map .catch . should_equal [My_Error 1]
Test.specify "should correctly propagate error through map" <|
[1, 2, 3].map Error.throw . catch . should_equal 1
fun a = if a == 3 then Error.throw (My_Error a) else a
[1, 2, 3, 4].map fun . catch My_Error . should_equal (My_Error 3)
Test.specify "should be able to be efficiently converted to a visualisation" <|
vec = Vector.fill 1000 0

View File

@ -8,9 +8,10 @@ import Standard.Test
import project.Helpers
spec =
expect value expected_json_text =
spec =
expect value expected_json_text =
result = Geo_Map.process_to_json_text value
IO.println result
Json.parse result . should_equal <| Json.parse expected_json_text
Test.group "Geo_Map" <|