Improve Vector (#3232)

This commit is contained in:
Radosław Waśko 2022-01-25 18:29:39 +01:00 committed by GitHub
parent f9e78a5caa
commit cfdb33bc68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 388 additions and 101 deletions

View File

@ -19,6 +19,10 @@
Vector][3224]
- [Implemented `Duration.time_execution` allowing timing of the execution of an
- expression within the UI][3229]
- [Improved performance of `Vector.filter` and `Vector.each`; implemented
`Vector.filter_with_index`. Made `Vector.at` accept negative indices and
ensured it fails with a dataflow error on out of bounds access instead of an
internal Java exception.][3232]
[3153]: https://github.com/enso-org/enso/pull/3153
[3166]: https://github.com/enso-org/enso/pull/3166
@ -28,6 +32,7 @@
[3208]: https://github.com/enso-org/enso/pull/3208
[3224]: https://github.com/enso-org/enso/pull/3224
[3229]: https://github.com/enso-org/enso/pull/3229
[3232]: https://github.com/enso-org/enso/pull/3232
# Enso 2.0.0-alpha.18 (2021-10-12)

View File

@ -11,3 +11,15 @@ type Pair
- first: The first element.
- second: The second element.
type Pair first second
## UNSTABLE
Applies the provided function to both elements of the pair.
> Example
Adding one to two elements of the pair.
(Pair 1 2).map (+1) == (Pair 2 3)
map : (Any -> Any) -> Pair
map fun =
Pair (fun this.first) (fun this.second)

View File

@ -126,14 +126,41 @@ type Vector
## Gets an element from the vector at a specified index (0-based).
Arguments:
- index: The location in the vector to get the element from.
- index: The location in the vector to get the element from. The index is
also allowed be negative, then the elements are indexed from the back
of the vector, i.e. -1 will correspond to the last element.
> Example
Get the second element of a vector.
[1, 2, 3].at 1
at : Number -> Any
at index = this.to_array.at index
[1, 2, 3].at 1 == 2
> Example
Get the last element of a vector.
[1, 2, 3].at -1 == 3
at : Integer -> Any ! Index_Out_Of_Bounds_Error
at index =
actual_index = if index < 0 then this.length + index else index
## TODO [RW] Ideally we do not want an additional check here, but we
should catch a Invalid_Array_Index_Error panic. However, such a catch
should still properly forward any other panics or dataflow errors
which is not fully possible until the approach to handling Panics is
improved, as described in the following Pivotal ticket:
https://www.pivotaltracker.com/n/projects/2539304/stories/181029230
if actual_index>=0 && actual_index<this.length then this.unsafe_at actual_index else
Error.throw (Index_Out_Of_Bounds_Error index this.length)
## ADVANCED
UNSTABLE
An unsafe variant of the `at` operation. It only allows non-negative
indices and will panic with a raw Java exception on out-of-bounds access.
Thus it should only be used when the access is guaranteed to be within
bounds or with additional error handling.
unsafe_at : Integer -> Any
unsafe_at index =
this.to_array.at index
## Combines all the elements of the vector, by iteratively applying the
passed function with next elements of the vector.
@ -206,7 +233,7 @@ type Vector
exists predicate =
len = this.length
go idx found = if found || (idx >= len) then found else
@Tail_Call go idx+1 (predicate (this.at idx))
@Tail_Call go idx+1 (predicate (this.unsafe_at idx))
go 0 False
## Returns the first element of the vector that satisfies the predicate or
@ -226,8 +253,8 @@ type Vector
len = this.length
go idx =
if (idx >= len) then Error.throw Nothing else
elem = this.at idx
if (predicate elem) then elem else
elem = this.unsafe_at idx
if predicate elem then elem else
@Tail_Call go idx+1
go 0
@ -302,8 +329,88 @@ type Vector
[1, 2, 3, 4, 5].filter (> 3)
filter : (Any -> Boolean) -> Vector Any
filter predicate =
check acc ix = if predicate ix then acc + [ix] else acc
this.fold [] check
builder = this.fold here.new_builder builder-> elem->
if predicate elem then builder.append elem else builder
builder.to_vector
## Selects all elements of this vector which satisfy a predicate.
Arguments:
- predicate: A function that takes an index and a list element and
returns a boolean value that says whether that value should be included
in the result.
> Example
Selecting all elements which are equal to their position in the vector.
[0, 10, 2, 2].filter (==) == [0, 2]
filter_with_index : (Integer -> Any -> Boolean) -> Vector Any
filter_with_index predicate =
acc = this.fold (Pair here.new_builder 0) acc-> elem->
builder = acc.first
ix = acc.second
new_builder = if predicate ix elem then builder.append elem else builder
Pair new_builder ix+1
builder = acc.first
builder.to_vector
## Partitions the vector into vectors of elements which satisfy a given
predicate and ones that do not.
Returns a `Pair` whose `first` element is the vector of elements
satisfying the predicate and the `second` element is a vector of elements
which did not satisfy it.
The relative order of elements kept in each returned list is the same as
in the input vector.
Arguments:
- predicate: A function that takes an element and returns a boolean
value.
> Example
Splitting a vector into even and odd elements.
[1, 2, 3, 4, 5].partition (x -> x % 2 == 0) == (Pair [2, 4] [1, 3, 5])
partition : (Any -> Boolean) -> Vector Any
partition predicate =
acc = this.fold (Pair here.new_builder here.new_builder) acc-> elem->
case predicate elem of
True ->
Pair (acc.first.append elem) acc.second
False ->
Pair acc.first (acc.second.append elem)
acc.map .to_vector
## Partitions the vector into vectors of elements which satisfy a given
predicate and ones that do not.
Returns a `Pair` whose `first` element is the vector of elements
satisfying the predicate and the `second` element is a vector of elements
which did not satisfy it.
The relative order of elements kept in each returned list is the same as
in the input vector.
Arguments:
- predicate: A function that takes an index and an element and returns a
boolean value.
> Example
Splitting a vector into elements at even and odd positions.
["a", "b", "c", "d"].partition_with_index (ix -> _ -> ix % 2 == 0) == (Pair ["a", "c"] ["b", "d"])
partition_with_index : (Integer -> Any -> Boolean) -> Vector Any
partition_with_index predicate =
acc = this.fold (Partition_Accumulator here.new_builder here.new_builder 0) acc-> elem->
case predicate acc.ix elem of
True ->
Partition_Accumulator (acc.true_builder.append elem) acc.false_builder acc.ix+1
False ->
Partition_Accumulator acc.true_builder (acc.false_builder.append elem) acc.ix+1
case acc of
Partition_Accumulator true_builder false_builder _ ->
Pair true_builder.to_vector false_builder.to_vector
## Applies a function to each element of the vector, returning the vector of
results.
@ -318,7 +425,7 @@ type Vector
[1, 2, 3] . map +1
map : (Any -> Any) -> Vector Any
map function =
here.new this.length i-> function (this.at i)
here.new this.length i-> function (this.unsafe_at i)
## Applies a function to each element of the vector, returning the vector
that contains all results concatenated.
@ -356,7 +463,7 @@ type Vector
[1, 2, 3].map_with_index (+)
map_with_index : (Integer -> Any -> Any) -> Vector Any
map_with_index function = here.new this.length i-> function i (this.at i)
map_with_index function = here.new this.length i-> function i (this.unsafe_at i)
## Applies a function to each element of the vector.
@ -372,8 +479,8 @@ type Vector
[1, 2, 3, 4, 5] . each IO.println
each : (Any -> Any) -> Nothing
each f =
this.map f
Nothing
0.up_to this.length . each ix->
f (this.unsafe_at ix)
## Reverses the vector, returning a vector with the same elements, but in
the opposite order.
@ -383,7 +490,7 @@ type Vector
[1, 2].reverse
reverse : Vector Any
reverse = here.new this.length (i -> this.at (this.length - (1 + i)))
reverse = here.new this.length (i -> this.unsafe_at (this.length - (1 + i)))
## Generates a human-readable text representation of the vector.
@ -392,12 +499,30 @@ type Vector
[1, 2, 3].to_text == "[1, 2, 3]"
to_text : Text
to_text =
if this.length == 0 then "[]" else
if this.length == 1 then "[" + (this.at 0 . to_text) + "]" else
folder = str -> ix -> str + ", " + (this.at ix).to_text
tail_elems = 1.up_to this.length . fold "" folder
"[" + (this.at 0 . to_text) + tail_elems + "]"
to_text = this.map .to_text . join ", " "[" "]"
## UNSTABLE
Generates a human-readable text representation of the vector, keeping its
length limited.
Arguments:
- max_entries: The maximum number of entries that are displayed. If the
vector contains more elements, the number of hidden elements is also
displayed.
> Example
Convert a large vector of numbers to a short text.
(0.up_to 100).to_vector.short_display_text max_entries=2 == "[0, 1 and 98 more elements]"
short_display_text : Integer -> Text
short_display_text max_entries=10 =
if max_entries < 1 then Error.throw <| Illegal_Argument_Error "The `max_entries` parameter must be positive." else
prefix = this.take_start max_entries
if prefix.length == this.length then this.to_text else
remaining_count = this.length - prefix.length
remaining_text = if remaining_count == 1 then "and 1 more element" else
"and " + remaining_count.to_text + " more elements"
prefix.map .to_text . join ", " "[" " "+remaining_text+"]"
## Checks whether this vector is equal to `that`.
@ -413,7 +538,7 @@ type Vector
[1, 2, 3] == [2, 3, 4]
== : Vector -> Boolean
== that =
eq_at i = this.at i == that.at i
eq_at i = this.unsafe_at i == that.unsafe_at i
if this.length == that.length then 0.up_to this.length . all eq_at else False
## Concatenates two vectors, resulting in a new vector, containing all the
@ -431,9 +556,9 @@ type Vector
this_len = this.length
arr = Array.new (this_len + that.length)
0.up_to this_len . each i->
arr.set_at i (this.at i)
arr.set_at i (this.unsafe_at i)
this.length.up_to arr.length . each i->
arr.set_at i (that.at i-this_len)
arr.set_at i (that.unsafe_at i-this_len)
Vector arr
## Add `element` to the beginning of `this` vector.
@ -464,17 +589,19 @@ type Vector
interspersing them with `separator`.
Arguments:
- separator: The text to use to join the textual elements of the vector.
- separator (optional): The text to use to join the textual elements of the vector.
- prefix (optional): The prefix to add at the beginning.
- suffix (optional): The suffix to add at the end.
> Example
Join the elements of the vector together as a string.
["foo", "bar", "baz"].join ", "
join : String -> Text
join separator =
if this.length == 0 then "" else
if this.length == 1 then this.at 0 else
this.at 0 + (1.up_to this.length . fold "" acc-> i-> acc + separator + this.at i)
join : Text -> Text
join separator="" prefix="" suffix="" =
if this.length == 0 then prefix+suffix else
if this.length == 1 then prefix + this.unsafe_at 0 + suffix else
prefix + this.unsafe_at 0 + (1.up_to this.length . fold "" acc-> i-> acc + separator + this.unsafe_at i) + suffix
## Creates a new vector with the first `count` elements in `this` removed.
@ -487,7 +614,7 @@ type Vector
[1, 2, 3, 4, 5].drop_start 1
drop_start : Integer -> Vector Any
drop_start count = if count >= this.length then here.new 0 (x -> x) else
here.new (this.length - count) (i -> this.at i+count)
here.new (this.length - count) (i -> this.unsafe_at i+count)
## Creates a new vector with the last `count` elements in `this` removed.
@ -554,7 +681,7 @@ type Vector
zip : Vector Any -> (Any -> Any -> Any) -> Vector Any
zip that function=[_,_] =
len = Math.min this.length that.length
here.new len i-> function (this.at i) (that.at i)
here.new len i-> function (this.unsafe_at i) (that.unsafe_at i)
## Extend `this` vector to the length of `n` appending elements `elem` to
the end.
@ -597,7 +724,7 @@ type Vector
[1, 2, 3, 4].head
head : Any ! Empty_Error
head = if this.length >= 1 then this.at 0 else Error.throw Empty_Error
head = if this.length >= 1 then this.unsafe_at 0 else Error.throw Empty_Error
## Get all elements in the vector except the first.
@ -626,7 +753,7 @@ type Vector
[1, 2, 3, 4].last
last : Vector ! Empty_Error
last = if this.length >= 1 then (this.take_end 1).at 0 else
last = if this.length >= 1 then this.unsafe_at (this.length-1) else
Error.throw Empty_Error
## Get the first element from the vector, or an `Empty_Error` if the vector
@ -649,7 +776,7 @@ type Vector
[1, 2, 3, 4].second
second : Vector ! Singleton_Error
second = if this.length >= 2 then this.at 1 else
second = if this.length >= 2 then this.unsafe_at 1 else
Error.throw (Singleton_Error this)
## Get all elements in the vector except the first.
@ -813,13 +940,12 @@ type Builder
Construct a vector using a builder that contains the items 1 to 10.
example_new_builder =
builder = Vector.new_builder
do_build start stop =
builder.append start
if start >= stop then Nothing else
@Tail_Call do_build start+1 stop
do_build 1 10
builder.to_vector
do_build builder start stop =
new_builder = builder.append start
if start >= stop then new_builder else
@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
## Creates a new builder.
@ -841,7 +967,33 @@ type Builder
capacity : Integer
capacity = this.to_array.length
## Appends a new element into this builder.
## Appends a new element into this builder and returns it, propagating any
errors that the provided element could have contained.
Arguments:
- item: The item to append to the vector builder.
> Example
Append two items.
builder = Vector.new_builder
builder . append 10 . append 20
append : Any ! Error -> Builder ! Error
append item = case item of
_ ->
this.unsafe_append item
this
## PRIVATE
Appends a new element into this builder.
? Propagating Dataflow Errors
Since this is an imperative operation which returns Nothing, if the
item to be appended contained a dataflow error, the operation will fail
and unless its result is inspected, the error can very easily be
ignored. To avoid this, prefer to use the `append` operation and
consume the returned builder which will contain any dataflow errors if
they need to be propagated.
Arguments:
- item: The item to append to the vector builder.
@ -849,9 +1001,9 @@ type Builder
> Example
Append an item to a vector builder.
Vector.new_builder.append 10
append : Any -> Nothing
append item = case this.capacity > this.length of
Vector.new_builder.unsafe_append 10
unsafe_append : Any -> Nothing
unsafe_append item = case this.capacity > this.length of
True ->
this.to_array.set_at this.length item
Unsafe.set_atom_field this 1 (this.length + 1)
@ -940,6 +1092,8 @@ Singleton_Error.to_display_text : Text
Singleton_Error.to_display_text =
"The vector " + this.vec.to_text + " has only one element."
## PRIVATE
type Partition_Accumulator true_builder false_builder ix
## UNSTABLE

View File

@ -12,3 +12,15 @@ type Illegal_State_Error
performed.
- cause: (optional) another error that is the cause of this one.
type Illegal_State_Error message cause=Nothing
type Illegal_Argument_Error
## UNSTABLE
A generic error that indicates that the provided argument is not valid
for the operation.
Arguments:
- message: the error message explaining why the argument is illegal.
- cause: (optional) another error that is the cause of this one.
type Illegal_Argument_Error message cause=Nothing

View File

@ -1,8 +1,11 @@
package org.enso.interpreter.node.expression.builtin.mutable;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.Language;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.runtime.builtin.Builtins;
import org.enso.interpreter.runtime.data.Array;
import org.enso.interpreter.runtime.error.PanicException;
@BuiltinMethod(
type = "Array",
@ -11,6 +14,11 @@ import org.enso.interpreter.runtime.data.Array;
public class GetAtNode extends Node {
Object execute(Array _this, long index) {
return _this.getItems()[(int) index];
try {
return _this.getItems()[(int) index];
} catch (IndexOutOfBoundsException exception) {
Builtins builtins = lookupContextReference(Language.class).get().getBuiltins();
throw new PanicException(builtins.error().makeInvalidArrayIndexError(_this, index), this);
}
}
}

View File

@ -1,9 +1,12 @@
package org.enso.interpreter.node.expression.builtin.mutable;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.Language;
import org.enso.interpreter.dsl.AcceptsError;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.runtime.builtin.Builtins;
import org.enso.interpreter.runtime.data.Array;
import org.enso.interpreter.runtime.error.PanicException;
@BuiltinMethod(
type = "Array",
@ -12,7 +15,12 @@ import org.enso.interpreter.runtime.data.Array;
public class SetAtNode extends Node {
Object execute(Array _this, long index, @AcceptsError Object value) {
_this.getItems()[(int) index] = value;
return _this;
try {
_this.getItems()[(int) index] = value;
return _this;
} catch (IndexOutOfBoundsException exception) {
Builtins builtins = lookupContextReference(Language.class).get().getBuiltins();
throw new PanicException(builtins.error().makeInvalidArrayIndexError(_this, index), this);
}
}
}

View File

@ -889,6 +889,10 @@ type Array
Arguments:
- index: The index to get the element from.
? Safety
If index < 0 or index >= this.length, then this operation will result
in an Invalid_Array_Index_Error exception.
> Example
Get the element at index 1.
@ -907,8 +911,8 @@ type Array
programming style in Enso.
? Safety
If index >= this.length, then this operation will result in an
Invalid_Array_Index_Error exception.
If index < 0 or index >= this.length, then this operation will result
in an Invalid_Array_Index_Error exception.
set_at : Integer -> Any -> Array
set_at index value = @Builtin_Method "Array.set_at"

View File

@ -6,68 +6,25 @@ polyglot java import java.util.Random
polyglot java import org.enso.base.Time_Utils
## Bench Utilities ============================================================
vector_size = 1000000
iter_size = 100
num_iterations = 10
make_sorted_ascending_vec : Integer -> Base.Vector.Vector
make_sorted_ascending_vec n = 0.up_to n+1 . to_vector
make_partially_sorted_vec : Integer -> Base.Vector.Vector
make_partially_sorted_vec n =
random_gen = Random.new n
direction = Ref.new Sort_Order.Ascending
last_num = Ref.new 0
run_length = Ref.new 0
Base.Vector.fill n <|
case (Ref.get run_length) == 0 of
True ->
new_direction = if random_gen.nextDouble > 0 then Sort_Order.Ascending else
Sort_Order.Descending
Ref.put direction new_direction
Ref.put run_length ((random_gen.nextLong % (n / 10).floor) - 1)
num = random_gen.nextInt
Ref.put last_num num
num
False ->
change = random_gen.nextInt.abs % n
num = case Ref.get direction of
Sort_Order.Ascending ->
num = (Ref.get last_num) + change
Ref.put last_num num
num
Sort_Order.Descending ->
num = (Ref.get last_num) - change
Ref.put last_num num
num
Ref.put run_length ((Ref.get run_length) - 1)
num
make_random_vec : Integer -> Base.Vector.Vector
make_random_vec n =
random_gen = Random.new n
Base.Vector.fill n random_gen.nextLong
# The Benchmarks ==============================================================
main =
sorted_vec = here.make_sorted_ascending_vec here.vector_size
partially_sorted_vec = here.make_partially_sorted_vec here.vector_size
random_vec = here.make_random_vec here.vector_size
projection = x -> x % 10
comparator = l -> r -> r.compare_to l
Bench.measure (sorted_vec.sort) "Already Sorted" here.iter_size here.num_iterations
Bench.measure (sorted_vec.sort order=Sort_Order.Descending) "Sorted in Opposite Order" here.iter_size here.num_iterations
Bench.measure (partially_sorted_vec.sort) "Sorted Runs Ascending" here.iter_size here.num_iterations
Bench.measure (partially_sorted_vec.sort order=Sort_Order.Descending) "Sorted Runs Descending" here.iter_size here.num_iterations
Bench.measure (random_vec.sort) "Random Elements Ascending" here.iter_size here.num_iterations
Bench.measure (random_vec.sort order=Sort_Order.Descending) "Random Elements Descending" here.iter_size here.num_iterations
Bench.measure (random_vec.sort on=projection) "Sorting with a Custom Projection" here.iter_size here.num_iterations
Bench.measure (random_vec.sort by=comparator) "Sorting with a Custom Comparison" here.iter_size here.num_iterations
Bench.measure (random_vec.filter (x -> x % 3 == 1)) "Filter" here.iter_size here.num_iterations
stateful_fun x =
s = State.get Number
State.put s+x
Bench.measure (State.run Number 0 <| random_vec.each stateful_fun) "Each" here.iter_size here.num_iterations

View File

@ -0,0 +1,68 @@
from Standard.Base import all
import Standard.Test.Bench
import project.Vector as Vector_Utils
polyglot java import java.util.Random
polyglot java import org.enso.base.Time_Utils
## Bench Utilities ============================================================
vector_size = 1000000
iter_size = 100
num_iterations = 10
make_sorted_ascending_vec : Integer -> Base.Vector.Vector
make_sorted_ascending_vec n = 0.up_to n+1 . to_vector
make_partially_sorted_vec : Integer -> Base.Vector.Vector
make_partially_sorted_vec n =
random_gen = Random.new n
direction = Ref.new Sort_Order.Ascending
last_num = Ref.new 0
run_length = Ref.new 0
Base.Vector.fill n <|
case (Ref.get run_length) == 0 of
True ->
new_direction = if random_gen.nextDouble > 0 then Sort_Order.Ascending else
Sort_Order.Descending
Ref.put direction new_direction
Ref.put run_length ((random_gen.nextLong % (n / 10).floor) - 1)
num = random_gen.nextInt
Ref.put last_num num
num
False ->
change = random_gen.nextInt.abs % n
num = case Ref.get direction of
Sort_Order.Ascending ->
num = (Ref.get last_num) + change
Ref.put last_num num
num
Sort_Order.Descending ->
num = (Ref.get last_num) - change
Ref.put last_num num
num
Ref.put run_length ((Ref.get run_length) - 1)
num
# The Benchmarks ==============================================================
main =
sorted_vec = here.make_sorted_ascending_vec here.vector_size
partially_sorted_vec = here.make_partially_sorted_vec here.vector_size
random_vec = Vector_Utils.make_random_vec here.vector_size
projection = x -> x % 10
comparator = l -> r -> r.compare_to l
Bench.measure (sorted_vec.sort) "Already Sorted" here.iter_size here.num_iterations
Bench.measure (sorted_vec.sort order=Sort_Order.Descending) "Sorted in Opposite Order" here.iter_size here.num_iterations
Bench.measure (partially_sorted_vec.sort) "Sorted Runs Ascending" here.iter_size here.num_iterations
Bench.measure (partially_sorted_vec.sort order=Sort_Order.Descending) "Sorted Runs Descending" here.iter_size here.num_iterations
Bench.measure (random_vec.sort) "Random Elements Ascending" here.iter_size here.num_iterations
Bench.measure (random_vec.sort order=Sort_Order.Descending) "Random Elements Descending" here.iter_size here.num_iterations
Bench.measure (random_vec.sort on=projection) "Sorting with a Custom Projection" here.iter_size here.num_iterations
Bench.measure (random_vec.sort by=comparator) "Sorting with a Custom Comparison" here.iter_size here.num_iterations

View File

@ -10,3 +10,19 @@ spec = Test.group "Arrays" <|
as_vec = json.into (Vector.Vector Number)
as_vec.should_equal <| Vector.fill 100 0
Test.specify "should allow accessing elements"
arr = [1, 2, 3] . to_array
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_array arr . should_equal [1, 10, 3]
Test.specify "should panic on out of bounds access"
arr = [1, 2, 3] . to_array
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

View File

@ -26,8 +26,23 @@ spec = Test.group "Vectors" <|
built . should_equal [1, 2, 3, 4, 5]
Test.specify "should allow accessing elements" <|
[1,2,3].at 0 . should_equal 1
[1,2,3].at 2 . should_equal 3
Test.specify "should allow to store dataflow errors and raise them on access" <|
vec = [Error.throw (My_Error "foo"), "bar"]
vec.at 1 . should_equal "bar"
vec.at 0 . should_fail_with My_Error
Test.specify "should allow accessing elements with negative indices" <|
[1,2,3].at -1 . should_equal 3
[1,2,3].at -2 . should_equal 2
[1,2,3].at -3 . should_equal 1
Test.specify "should return a dataflow error when accessing elements out of bounds" <|
[1,2,3].at -4 . should_fail_with Vector.Index_Out_Of_Bounds_Error
[1,2,3].at 3 . should_fail_with Vector.Index_Out_Of_Bounds_Error
Test.specify "should have a well-defined length" <|
[1,2,3].length . should_equal 3
@ -77,9 +92,25 @@ spec = Test.group "Vectors" <|
Test.specify "should filter elements" <|
vec = [1, 2, 3, 4, 5]
vec.filter (ix -> ix > 3) . should_equal [4, 5]
vec.filter (ix -> ix == 1) . should_equal [1]
vec.filter (ix -> ix < 0) . should_equal []
vec.filter (x -> x > 3) . should_equal [4, 5]
vec.filter (x -> x == 1) . should_equal [1]
vec.filter (x -> x < 0) . should_equal []
vec.filter (x -> if x == 2 then Error.throw <| My_Error "foo" else True) . should_fail_with My_Error
Test.specify "should filter elements with indices" <|
[0, 10, 2, 2].filter_with_index (==) . should_equal [0, 2]
([1, 2, 3, 4].filter_with_index ix-> _-> ix < 2) . should_equal [1, 2]
([1, 2, 3, 4].filter_with_index ix-> _-> if ix == 1 then Error.throw <| My_Error "foo" else True) . should_fail_with My_Error
Test.specify "should partition elements" <|
[1, 2, 3, 4, 5].partition (x -> x % 2 == 0) . should_equal <| Pair [2, 4] [1, 3, 5]
([1, 2, 3, 4].partition x-> if x == 1 then Error.throw <| My_Error "foo" else True) . should_fail_with My_Error
["a", "b", "c", "d"].partition_with_index (ix -> _ -> ix % 2 == 0) == (Pair ["a", "c"] ["b", "d"])
["a", "b", "c", "d"].partition_with_index (ix -> _ -> if ix % 2 == 0 then Error.throw <| My_Error "foo" else True) . should_fail_with My_Error
Test.specify "should allow to join a vector of text elements to form a single text" <|
["a", "b", "c"].join . should_equal "abc"
["a", "b", "c"].join ";" "{" "}" . should_equal "{a;b;c}"
Test.specify "should allow mapping an operation, returning a new vector" <|
vec = [1, 2, 3, 4]
@ -105,6 +136,18 @@ spec = Test.group "Vectors" <|
[].to_text.should_equal "[]"
[1,2,3].to_text.should_equal "[1, 2, 3]"
[Nothing].to_text.should_equal "[Nothing]"
['a'].to_text . should_equal "['a']"
Test.specify "should allow to generate a short text representation for display" <|
[].short_display_text max_entries=3 . should_equal "[]"
[1].short_display_text max_entries=3 . should_equal "[1]"
[1, 2].short_display_text max_entries=3 . should_equal "[1, 2]"
[1, 2, 3].short_display_text max_entries=3 . should_equal "[1, 2, 3]"
[1, 2, 3, 4].short_display_text max_entries=3 . should_equal "[1, 2, 3 and 1 more element]"
[1, 2, 3, 4, 5, 6].short_display_text max_entries=3 . should_equal "[1, 2, 3 and 3 more elements]"
(0.up_to 100).to_vector.short_display_text max_entries=2 . should_equal "[0, 1 and 98 more elements]"
[].short_display_text max_entries=0 . should_fail_with Illegal_Argument_Error
Test.specify "should define equality" <|
[1,2,3]==[1,2] . should_be_false