From 2c12a18b251e54789b4f5256d8b260dff3db9a46 Mon Sep 17 00:00:00 2001 From: Ara Adkins Date: Tue, 15 Dec 2020 14:20:59 +0000 Subject: [PATCH] Implement sorting for `Vector` (#1349) --- .../Base/src/Data/Number/Extensions.enso | 5 - .../std-lib/Base/src/Data/Ordering.enso | 17 ++- .../std-lib/Base/src/Data/Vector.enso | 48 +++++-- .../std-lib/Base/src/Data/Vector/Sort.enso | 15 --- distribution/std-lib/Base/src/Main.enso | 2 +- .../node/callable/MethodResolverNode.java | 2 +- .../builtin/mutable/ComparatorNode.java | 66 ++++++++++ .../expression/builtin/mutable/CopyNode.java | 33 +++++ .../expression/builtin/mutable/SortNode.java | 123 ++++++++++++++++++ .../number/bigInteger/CompareToNode.java | 71 ++++++++++ .../builtin/number/decimal/CompareToNode.java | 78 +++++++++++ .../number/smallInteger/CompareToNode.java | 81 ++++++++++++ .../builtin/number/utils/BigIntegerOps.java | 26 ++++ .../interpreter/runtime/builtin/Builtins.java | 42 +++--- .../interpreter/runtime/builtin/Error.java | 20 +++ .../interpreter/runtime/builtin/Mutable.java | 12 +- .../interpreter/runtime/builtin/Number.java | 15 +++ .../interpreter/runtime/builtin/Ordering.java | 76 +++++++++++ .../enso/interpreter/runtime/data/Array.java | 7 +- .../enso/compiler/codegen/IrToTruffle.scala | 2 +- .../pass/resolve/SuspendedArguments.scala | 2 +- .../main/java/org/enso/base/Time_Utils.java | 1 + test/Benchmarks/src/Vector.enso | 73 +++++++++++ test/Tests/src/Data/Noise_Spec.enso | 2 +- test/Tests/src/Data/Vector_Spec.enso | 47 ++++++- 25 files changed, 801 insertions(+), 65 deletions(-) delete mode 100644 distribution/std-lib/Base/src/Data/Vector/Sort.enso create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/ComparatorNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/CopyNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SortNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/CompareToNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/CompareToNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/CompareToNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Ordering.java create mode 100644 test/Benchmarks/src/Vector.enso diff --git a/distribution/std-lib/Base/src/Data/Number/Extensions.enso b/distribution/std-lib/Base/src/Data/Number/Extensions.enso index 5aa104ac8d..6b1b49a3c9 100644 --- a/distribution/std-lib/Base/src/Data/Number/Extensions.enso +++ b/distribution/std-lib/Base/src/Data/Number/Extensions.enso @@ -102,8 +102,3 @@ Decimal.parse : Text -> Decimal | Nothing Decimal.parse text = Panic.recover (Double.parseDouble [text]) . catch (_ -> Nothing) -## Compare two numbers. -Number.compare_to : Number -> Ordering -Number.compare_to that = if this == that then Ordering.Equal else - if this > that then Ordering.Greater else Ordering.Less - diff --git a/distribution/std-lib/Base/src/Data/Ordering.enso b/distribution/std-lib/Base/src/Data/Ordering.enso index bdcdfb5bba..0d1fd2fd71 100644 --- a/distribution/std-lib/Base/src/Data/Ordering.enso +++ b/distribution/std-lib/Base/src/Data/Ordering.enso @@ -1,5 +1,9 @@ from Base import all +from Builtins import Less, Equal, Greater + +from Builtins export Less, Equal, Greater + ## Types representing the ordering of values. These are intended to be returned from the `compare_to` function, that has a @@ -9,8 +13,15 @@ from Base import all `that`. So, if `this` is greater than `that`, you should return `Greater.` type Ordering ## An ordering where a compared value is less than another. - type Less + Less ## An ordering where a compared value is equal to another. - type Equal + Equal ## An ordering where a compared value is greater than another. - type Greater + Greater + + ## Converts the ordering to the signed notion of ordering based on integers. + to_sign : Integer + to_sign = case this of + Less -> -1 + Equal -> 0 + Greater -> 1 diff --git a/distribution/std-lib/Base/src/Data/Vector.enso b/distribution/std-lib/Base/src/Data/Vector.enso index 37ef9e1329..665a2c8576 100644 --- a/distribution/std-lib/Base/src/Data/Vector.enso +++ b/distribution/std-lib/Base/src/Data/Vector.enso @@ -1,8 +1,6 @@ from Builtins import Array from Base import all -import Base.Data.Vector.Sort - ## Creates a new vector of the given length, initializing elements using the provided constructor function. @@ -30,7 +28,7 @@ new length constructor = created by: Vector.fill length=50 item=42 fill : Number -> Any -> Vector -fill length item = +fill length ~item = arr = Array.new length 0.up_to length . each ix-> arr.set_at ix item Vector arr @@ -407,10 +405,7 @@ type Vector rest : Vector | Nothing rest = this.tail - ## PRIVATE - Doesn't work yet. - - Sort the Vector. + ## Sort the Vector. Arguments: - `on`: A projection from the element type to the value of that element @@ -420,10 +415,26 @@ type Vector - `order`: The order in which the vector elements are sorted. By default, elements are sorted in ascending order, using the comparator - `>`. A custom comparator may be passed to the sort function. + `compare_to`. A custom comparator may be passed to the sort function. - This implements a stable sort, meaning that items that compare the same - will not have their order changed. + This is a stable sort, meaning that items that compare the same will not + have their order changed by the sorting process. + + The complexities for this sort are: + - *Worst-Case Time:* `O(n * log n)` + - *Best-Case Time:* `O(n)` + - *Average Time:* `O(n * log n)` + - *Worst-Case Space:* `O(n)` additional + + ? Implementation Note + The sort implementation is based upon an adaptive, iterative mergesort + that requires far fewer than `n * log(n)` comparisons when the vector + is partially sorted. When the vector is randomly ordered, the + performance is equivalent to a standard mergesort. + + It takes equal advantage of ascending and descending runs in the array, + making it much simpler to merge two or more sorted arrays: simply + concatenate them and sort. > Example Sorting a vector of numbers. @@ -434,7 +445,22 @@ type Vector [Pair 1 2, Pair -1 8].sort (_.first) (order = Sort_Order.Descending) sort : (Any -> Any) -> (Any -> Any -> Ordering) -> Sort_Order -> Vector sort (on = x -> x) (by = (_.compare_to _)) (order = Sort_Order.Ascending) = - Sort.run this on by order + ## Prepare the destination array that will underlie the vector. We do + not want to sort in place on the original vector, as `sort` is not + intended to be mutable. + new_vec_arr = Array.new this.length + this.to_array.copy 0 new_vec_arr 0 this.length + + ## As we want to account for both custom projections and custom + comparisons we need to construct a comparator for internal use that + does both. + comp_ascending l r = by (on l) (on r) + comp_descending l r = by (on r) (on l) + compare = if order == Sort_Order.Ascending then comp_ascending else comp_descending + + new_vec_arr.sort compare + + Vector new_vec_arr ## A builder type for Enso vectors. diff --git a/distribution/std-lib/Base/src/Data/Vector/Sort.enso b/distribution/std-lib/Base/src/Data/Vector/Sort.enso deleted file mode 100644 index 526fd1f889..0000000000 --- a/distribution/std-lib/Base/src/Data/Vector/Sort.enso +++ /dev/null @@ -1,15 +0,0 @@ -from Base import all - -# TODO [AA] Implement vector sorting. - -## PRIVATE - - Execute the sort. - - In the use-cases we anticipate for Enso, we can see `sort` being run over - both partially-sorted data and random data. - - TODO [AA] Explain why we chose this sort. -run : Vector -> (Any -> Any) -> (Any -> Any -> Ordering) -> Sort_Order -> Vector -run vec _ _ _ = vec - diff --git a/distribution/std-lib/Base/src/Main.enso b/distribution/std-lib/Base/src/Main.enso index d4c6b80be5..77980fc65b 100644 --- a/distribution/std-lib/Base/src/Main.enso +++ b/distribution/std-lib/Base/src/Main.enso @@ -40,5 +40,5 @@ from Base.Data.Text.Extensions export Text from Base.Error.Extensions export all from Base.Meta.Enso_Project export all from Base.Polyglot.Java export all -from Builtins export all hiding Meta +from Builtins export all hiding Meta, Less, Equal, Greater, Ordering diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/MethodResolverNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/MethodResolverNode.java index 87e1dc03d9..d687f59c01 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/MethodResolverNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/MethodResolverNode.java @@ -423,7 +423,7 @@ public abstract class MethodResolverNode extends Node { @CompilerDirectives.TruffleBoundary Function resolveMethodOnArray(Context context, UnresolvedSymbol symbol) { return symbol.resolveFor( - context.getBuiltins().mutable().constructor(), context.getBuiltins().any()); + context.getBuiltins().mutable().array(), context.getBuiltins().any()); } @CompilerDirectives.TruffleBoundary diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/ComparatorNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/ComparatorNode.java new file mode 100644 index 0000000000..9f9a0ca038 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/ComparatorNode.java @@ -0,0 +1,66 @@ +package org.enso.interpreter.node.expression.builtin.mutable; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.TruffleLanguage.ContextReference; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.CachedContext; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.nodes.NodeInfo; +import org.enso.interpreter.Language; +import org.enso.interpreter.node.callable.InvokeCallableNode; +import org.enso.interpreter.node.callable.InvokeCallableNode.ArgumentsExecutionMode; +import org.enso.interpreter.node.callable.InvokeCallableNode.DefaultsExecutionMode; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; +import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.error.PanicException; +import org.enso.interpreter.runtime.state.Stateful; +import org.enso.interpreter.runtime.state.data.EmptyMap; + +@NodeInfo( + shortName = "sortComparator", + description = "The implementation of the comparator for Array sorting.") +public abstract class ComparatorNode extends Node { + private @Child InvokeCallableNode invokeNode; + + public static ComparatorNode build() { + return ComparatorNodeGen.create(); + } + + ComparatorNode() { + CallArgumentInfo[] callArguments = {new CallArgumentInfo(), new CallArgumentInfo()}; + invokeNode = + InvokeCallableNode.build( + callArguments, DefaultsExecutionMode.EXECUTE, ArgumentsExecutionMode.PRE_EXECUTED); + } + + abstract int execute(VirtualFrame frame, Object comparator, Object l, Object r); + + @Specialization + int execute( + VirtualFrame frame, + Object comparator, + Object l, + Object r, + @CachedContext(Language.class) ContextReference ctxRef, + @Cached("ctxRef.get().getBuiltins().ordering().newLess()") Atom less, + @Cached("ctxRef.get().getBuiltins().ordering().newEqual()") Atom equal, + @Cached("ctxRef.get().getBuiltins().ordering().newGreater()") Atom greater) { + Stateful result = invokeNode.execute(comparator, frame, EmptyMap.create(), new Object[] {l, r}); + Object atom = result.getValue(); + if (atom == less) { + return -1; + } else if (atom == equal) { + return 0; + } else if (atom == greater) { + return 1; + } else { + CompilerDirectives.transferToInterpreter(); + var ordering = ctxRef.get().getBuiltins().ordering().ordering(); + throw new PanicException( + ctxRef.get().getBuiltins().error().makeTypeError(ordering, result.getValue()), this); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/CopyNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/CopyNode.java new file mode 100644 index 0000000000..67b18280c2 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/CopyNode.java @@ -0,0 +1,33 @@ +package org.enso.interpreter.node.expression.builtin.mutable; + +import com.oracle.truffle.api.TruffleLanguage.ContextReference; +import com.oracle.truffle.api.dsl.CachedContext; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.Language; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.data.Array; + +@BuiltinMethod(type = "Array", name = "copy", description = "Copies one array to another.") +public abstract class CopyNode extends Node { + + static CopyNode build() { + return CopyNodeGen.create(); + } + + abstract Object execute(Array _this, long source_index, Array that, long dest_index, long count); + + @Specialization + Object doArray( + Array _this, + long source_index, + Array that, + long dest_index, + long count, + @CachedContext(Language.class) ContextReference ctxRef) { + System.arraycopy( + _this.getItems(), (int) source_index, that.getItems(), (int) dest_index, (int) count); + return ctxRef.get().getBuiltins().nothing().newInstance(); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SortNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SortNode.java new file mode 100644 index 0000000000..9567fc986b --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SortNode.java @@ -0,0 +1,123 @@ +package org.enso.interpreter.node.expression.builtin.mutable; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.TruffleLanguage.ContextReference; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.CachedContext; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.LoopNode; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.profiles.BranchProfile; +import java.util.Arrays; +import java.util.Comparator; +import org.enso.interpreter.Language; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.data.Array; +import org.enso.interpreter.runtime.error.PanicException; +import org.enso.interpreter.runtime.state.Stateful; +import org.enso.interpreter.runtime.state.data.EmptyMap; + +@BuiltinMethod(type = "Array", name = "sort", description = "Sorts a mutable array in place.") +public abstract class SortNode extends Node { + private @Child ComparatorNode comparatorNode = ComparatorNode.build(); + private final BranchProfile resultProfile = BranchProfile.create(); + + abstract Object execute(VirtualFrame frame, Object _this, Object comparator); + + static SortNode build() { + return SortNodeGen.create(); + } + + @Specialization + Object doSortFunction( + VirtualFrame frame, + Array _this, + Function comparator, + @CachedContext(Language.class) ContextReference ctxRef) { + Comparator compare = getComparator(comparator, ctxRef); + return runSort(compare, _this, ctxRef); + } + + @Specialization + Object doSortCallable( + VirtualFrame frame, + Array _this, + Object comparator, + @CachedContext(Language.class) ContextReference ctxRef) { + Comparator compare = (l, r) -> comparatorNode.execute(frame, comparator, l, r); + return runSort(compare, _this, ctxRef); + } + + @Specialization + Object doAtomThis( + VirtualFrame frame, + Atom _this, + Object that, + @CachedContext(Language.class) ContextReference ctxRef, + @Cached("ctxRef.get().getBuiltins().mutable().array()") AtomConstructor array) { + return ctxRef.get().getBuiltins().nothing().newInstance(); + } + + Object runSort(Comparator compare, Array _this, ContextReference ctxRef) { + doSort(_this.getItems(), compare); + LoopNode.reportLoopCount(this, _this.length()); + return ctxRef.get().getBuiltins().nothing().newInstance(); + } + + @TruffleBoundary + void doSort(Object[] items, Comparator compare) { + Arrays.sort(items, compare); + } + + private SortComparator getComparator(Function comp, ContextReference ctxRef) { + return new SortComparator(comp, ctxRef, this); + } + + private class SortComparator implements Comparator { + private final Function compFn; + private final ContextReference ctxRef; + private final Atom less; + private final Atom equal; + private final Atom greater; + private final SortNode outerThis; + + SortComparator(Function compFn, ContextReference ctxRef, SortNode outerThis) { + this.compFn = compFn; + this.ctxRef = ctxRef; + this.less = ctxRef.get().getBuiltins().ordering().newLess(); + this.equal = ctxRef.get().getBuiltins().ordering().newEqual(); + this.greater = ctxRef.get().getBuiltins().ordering().newGreater(); + this.outerThis = outerThis; + } + + @Override + public int compare(Object o1, Object o2) { + Object[] args = + Function.ArgumentsHelper.buildArguments( + compFn, null, EmptyMap.create(), new Object[] {o1, o2}); + Stateful result = (Stateful) (compFn).getCallTarget().call(args); + Object value = result.getValue(); + return convertResult(value); + } + + private int convertResult(Object res) { + if (res == less) { + return -1; + } else if (res == equal) { + return 0; + } else if (res == greater) { + return 1; + } else { + resultProfile.enter(); + var ordering = ctxRef.get().getBuiltins().ordering().ordering(); + throw new PanicException( + ctxRef.get().getBuiltins().error().makeTypeError(ordering, res), outerThis); + } + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/CompareToNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/CompareToNode.java new file mode 100644 index 0000000000..b31bea53b9 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/CompareToNode.java @@ -0,0 +1,71 @@ +package org.enso.interpreter.node.expression.builtin.number.bigInteger; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.TruffleLanguage.ContextReference; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.CachedContext; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.Language; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.builtin.Ordering; +import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.error.PanicException; +import org.enso.interpreter.runtime.number.EnsoBigInteger; + +@BuiltinMethod( + type = "Big_Integer", + name = "compare_to", + description = "Comparison for big integers.") +public abstract class CompareToNode extends Node { + + static CompareToNode build() { + return CompareToNodeGen.create(); + } + + abstract Atom execute(EnsoBigInteger _this, Object that); + + @Specialization + Atom doLong( + EnsoBigInteger _this, + long that, + @CachedContext(Language.class) ContextReference ctxRef, + @Cached("getOrdering(ctxRef)") Ordering ordering) { + return ordering.fromJava(BigIntegerOps.compareTo(_this.getValue(), that)); + } + + @Specialization + Atom doBigInt( + EnsoBigInteger _this, + EnsoBigInteger that, + @CachedContext(Language.class) ContextReference ctxRef, + @Cached("getOrdering(ctxRef)") Ordering ordering) { + return ordering.fromJava(BigIntegerOps.compareTo(_this.getValue(), that.getValue())); + } + + @Specialization + Atom doDecimal( + EnsoBigInteger _this, + double that, + @CachedContext(Language.class) ContextReference ctxRef, + @Cached("getOrdering(ctxRef)") Ordering ordering) { + return ordering.fromJava(BigIntegerOps.compareTo(_this.getValue(), that)); + } + + @Specialization + Atom doOther( + EnsoBigInteger _this, + Object that, + @CachedContext(Language.class) ContextReference ctxRef) { + CompilerDirectives.transferToInterpreter(); + var number = ctxRef.get().getBuiltins().number().getNumber().newInstance(); + var typeError = ctxRef.get().getBuiltins().error().makeTypeError(that, number); + throw new PanicException(typeError, this); + } + + Ordering getOrdering(ContextReference ctxRef) { + return ctxRef.get().getBuiltins().ordering(); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/CompareToNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/CompareToNode.java new file mode 100644 index 0000000000..a13d65a9ef --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/CompareToNode.java @@ -0,0 +1,78 @@ +package org.enso.interpreter.node.expression.builtin.number.decimal; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.TruffleLanguage.ContextReference; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.CachedContext; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.Language; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.builtin.Ordering; +import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.error.PanicException; +import org.enso.interpreter.runtime.number.EnsoBigInteger; + +@BuiltinMethod(type = "Decimal", name = "compare_to", description = "Comparison for decimals.") +public abstract class CompareToNode extends Node { + + static CompareToNode build() { + return CompareToNodeGen.create(); + } + + abstract Atom execute(double _this, Object that); + + @Specialization + Atom doLong( + double _this, + long that, + @CachedContext(Language.class) ContextReference ctxRef, + @Cached("getOrdering(ctxRef)") Ordering ordering) { + if (_this == that) { + return ordering.newEqual(); + } else if (_this > that) { + return ordering.newGreater(); + } else { + return ordering.newLess(); + } + } + + @Specialization + Atom doBigInt( + double _this, + EnsoBigInteger that, + @CachedContext(Language.class) ContextReference ctxRef, + @Cached("getOrdering(ctxRef)") Ordering ordering) { + return ordering.fromJava(BigIntegerOps.compareTo(_this, that.getValue())); + } + + @Specialization + Atom doDecimal( + double _this, + double that, + @CachedContext(Language.class) ContextReference ctxRef, + @Cached("getOrdering(ctxRef)") Ordering ordering) { + if (_this == that) { + return ordering.newEqual(); + } else if (_this > that) { + return ordering.newGreater(); + } else { + return ordering.newLess(); + } + } + + @Specialization + Atom doOther( + double _this, Object that, @CachedContext(Language.class) ContextReference ctxRef) { + CompilerDirectives.transferToInterpreter(); + var number = ctxRef.get().getBuiltins().number().getNumber().newInstance(); + var typeError = ctxRef.get().getBuiltins().error().makeTypeError(that, number); + throw new PanicException(typeError, this); + } + + Ordering getOrdering(ContextReference ctxRef) { + return ctxRef.get().getBuiltins().ordering(); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/CompareToNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/CompareToNode.java new file mode 100644 index 0000000000..bad4eeff3b --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/CompareToNode.java @@ -0,0 +1,81 @@ +package org.enso.interpreter.node.expression.builtin.number.smallInteger; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.TruffleLanguage.ContextReference; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.CachedContext; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.Language; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.builtin.Ordering; +import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.error.PanicException; +import org.enso.interpreter.runtime.number.EnsoBigInteger; + +@BuiltinMethod( + type = "Small_Integer", + name = "compare_to", + description = "Comparison for small integers.") +public abstract class CompareToNode extends Node { + + static CompareToNode build() { + return CompareToNodeGen.create(); + } + + abstract Atom execute(long _this, Object that); + + @Specialization + Atom doLong( + long _this, + long that, + @CachedContext(Language.class) ContextReference ctxRef, + @Cached("getOrdering(ctxRef)") Ordering ordering) { + if (_this == that) { + return ordering.newEqual(); + } else if (_this > that) { + return ordering.newGreater(); + } else { + return ordering.newLess(); + } + } + + @Specialization + Atom doBigInt( + long _this, + EnsoBigInteger that, + @CachedContext(Language.class) ContextReference ctxRef, + @Cached("getOrdering(ctxRef)") Ordering ordering) { + return ordering.fromJava(BigIntegerOps.compareTo(_this, that.getValue())); + } + + @Specialization + Atom doDecimal( + long _this, + double that, + @CachedContext(Language.class) ContextReference ctxRef, + @Cached("getOrdering(ctxRef)") Ordering ordering) { + if (_this == that) { + return ordering.newEqual(); + } else if (_this > that) { + return ordering.newGreater(); + } else { + return ordering.newLess(); + } + } + + @Specialization + Atom doOther( + long _this, Object that, @CachedContext(Language.class) ContextReference ctxRef) { + CompilerDirectives.transferToInterpreter(); + var number = ctxRef.get().getBuiltins().number().getNumber().newInstance(); + var typeError = ctxRef.get().getBuiltins().error().makeTypeError(that, number); + throw new PanicException(typeError, this); + } + + Ordering getOrdering(ContextReference ctxRef) { + return ctxRef.get().getBuiltins().ordering(); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/utils/BigIntegerOps.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/utils/BigIntegerOps.java index 7d3f983013..f803b16f20 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/utils/BigIntegerOps.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/utils/BigIntegerOps.java @@ -3,6 +3,7 @@ package org.enso.interpreter.node.expression.builtin.number.utils; import com.fasterxml.jackson.databind.node.BigIntegerNode; import com.oracle.truffle.api.CompilerDirectives; +import java.math.BigDecimal; import java.math.BigInteger; /** Re-exposes big-integer operations behind a truffle boundary. */ @@ -204,6 +205,31 @@ public class BigIntegerOps { return BigIntegerOps.compare(a,BigInteger.ZERO) == 0; } + @CompilerDirectives.TruffleBoundary + public static int compareTo(long a, BigInteger b) { + return -b.signum(); + } + + @CompilerDirectives.TruffleBoundary + public static int compareTo(BigInteger a, long b) { + return a.signum(); + } + + @CompilerDirectives.TruffleBoundary + public static int compareTo(BigInteger a, BigInteger b) { + return a.compareTo(b); + } + + @CompilerDirectives.TruffleBoundary + public static int compareTo(BigInteger a, double b) { + return (new BigDecimal(a)).compareTo(BigDecimal.valueOf(b)); + } + + @CompilerDirectives.TruffleBoundary + public static int compareTo(double a, BigInteger b) { + return BigDecimal.valueOf(a).compareTo(new BigDecimal(b)); + } + @CompilerDirectives.TruffleBoundary public static boolean fitsInLong(BigInteger bigInteger) { return bigInteger.compareTo(MIN_LONG_BIGINT) >= 0 && bigInteger.compareTo(MAX_LONG_BIGINT) <= 0; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java index 225c4c7e6a..a801225770 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java @@ -38,22 +38,24 @@ public class Builtins { } } - private final Module module; - private final ModuleScope scope; - private final AtomConstructor nothing; private final AtomConstructor any; - private final Number number; - private final AtomConstructor function; private final AtomConstructor debug; private final AtomConstructor ensoProject; - private final Text text; - private final Error error; + private final AtomConstructor function; + private final AtomConstructor nothing; + private final Bool bool; - private final System system; + private final Error error; + private final Meta meta; + private final Module module; + private final ModuleScope scope; private final Mutable mutable; + private final Number number; + private final Ordering ordering; private final Polyglot polyglot; private final Resource resource; - private final Meta meta; + private final System system; + private final Text text; /** * Creates an instance with builtin methods installed. @@ -62,27 +64,28 @@ public class Builtins { */ public Builtins(Context context) { Language language = context.getLanguage(); - module = Module.empty(QualifiedName.fromString(MODULE_NAME).get()); scope = module.compileScope(context); - nothing = new AtomConstructor("Nothing", scope).initializeFields(); + any = new AtomConstructor("Any", scope).initializeFields(); bool = new Bool(language, scope); - error = new Error(language, scope); - mutable = new Mutable(language, scope); - function = new AtomConstructor("Function", scope).initializeFields(); - text = new Text(language, scope); debug = new AtomConstructor("Debug", scope).initializeFields(); ensoProject = new AtomConstructor("Enso_Project", scope) .initializeFields( new ArgumentDefinition( 0, "prim_root_file", ArgumentDefinition.ExecutionMode.EXECUTE)); - system = new System(language, scope); + error = new Error(language, scope); + function = new AtomConstructor("Function", scope).initializeFields(); + meta = new Meta(language, scope); + mutable = new Mutable(language, scope); + nothing = new AtomConstructor("Nothing", scope).initializeFields(); number = new Number(language, scope); + ordering = new Ordering(language, scope); polyglot = new Polyglot(language, scope); resource = new Resource(language, scope); - meta = new Meta(language, scope); + system = new System(language, scope); + text = new Text(language, scope); AtomConstructor nil = new AtomConstructor("Nil", scope).initializeFields(); AtomConstructor cons = @@ -243,6 +246,11 @@ public class Builtins { return polyglot; } + /** @return the container for ordering-related builtins */ + public Ordering ordering() { + return ordering; + } + /** * Returns the builtin module scope. * diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java index 5604152ef0..9646cbf8e6 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java @@ -11,6 +11,7 @@ import org.enso.interpreter.runtime.scope.ModuleScope; /** Container for builtin Error types */ public class Error { private final AtomConstructor syntaxError; + private final AtomConstructor typeError; private final AtomConstructor compileError; private final AtomConstructor inexhaustivePatternMatchError; private final AtomConstructor uninitializedState; @@ -34,6 +35,11 @@ public class Error { new AtomConstructor("Syntax_Error", scope) .initializeFields( new ArgumentDefinition(0, "message", ArgumentDefinition.ExecutionMode.EXECUTE)); + typeError = + new AtomConstructor("Type_Error", scope) + .initializeFields( + new ArgumentDefinition(0, "expected", ArgumentDefinition.ExecutionMode.EXECUTE), + new ArgumentDefinition(0, "actual", ArgumentDefinition.ExecutionMode.EXECUTE)); compileError = new AtomConstructor("Compile_Error", scope) .initializeFields( @@ -78,6 +84,9 @@ public class Error { return syntaxError; } + /** @return the builtin {@code Type_Error} atom constructor. */ + public AtomConstructor typeError() { return typeError; } + /** @return the builtin {@code Compile_Error} atom constructor. */ public AtomConstructor compileError() { return compileError; @@ -109,6 +118,17 @@ public class Error { return noSuchMethodError.newInstance(target, symbol); } + /** + * Creates an instance of the runtime representation of a {@code Type_Error}. + * + * @param expected the expected type + * @param actual the actual type + * @return a runtime representation of the error. + */ + public Atom makeTypeError(Object expected, Object actual) { + return typeError.newInstance(expected, actual); + } + /** * Creates an instance of the runtime representation of a {@code Polyglot_Error}. * diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Mutable.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Mutable.java index df77be1386..3e055e01e2 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Mutable.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Mutable.java @@ -8,6 +8,7 @@ import org.enso.interpreter.runtime.scope.ModuleScope; /** Container for builtin array-related types and functions. */ public class Mutable { private final AtomConstructor array; + private final AtomConstructor ref; /** * Creates a new instance of this class, registering all relevant bindings in the provided scope. @@ -28,8 +29,10 @@ public class Mutable { scope.registerMethod(array, "to_array", ToArrayMethodGen.makeFunction(language)); scope.registerMethod(array, "at", GetAtMethodGen.makeFunction(language)); scope.registerMethod(array, "set_at", SetAtMethodGen.makeFunction(language)); + scope.registerMethod(array, "copy", CopyMethodGen.makeFunction(language)); + scope.registerMethod(array, "sort", SortMethodGen.makeFunction(language)); - AtomConstructor ref = new AtomConstructor("Ref", scope).initializeFields(); + ref = new AtomConstructor("Ref", scope).initializeFields(); scope.registerConstructor(ref); scope.registerMethod(ref, "new", NewRefMethodGen.makeFunction(language)); scope.registerMethod(ref, "get", GetRefMethodGen.makeFunction(language)); @@ -37,7 +40,12 @@ public class Mutable { } /** @return the Array constructor. */ - public AtomConstructor constructor() { + public AtomConstructor array() { return array; } + + /** @return the Ref constructor. */ + public AtomConstructor ref() { + return ref; + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Number.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Number.java index 6a0046529f..c63a4bf075 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Number.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Number.java @@ -157,6 +157,11 @@ public class Number { "bit_shift_r", org.enso.interpreter.node.expression.builtin.number.smallInteger.BitShiftRightMethodGen .makeFunction(language)); + scope.registerMethod( + smallInteger, + "compare_to", + org.enso.interpreter.node.expression.builtin.number.smallInteger.CompareToMethodGen + .makeFunction(language)); } private void registerBigIntegerMethods(Language language, ModuleScope scope) { @@ -281,6 +286,11 @@ public class Number { "bit_shift_r", org.enso.interpreter.node.expression.builtin.number.bigInteger.BitShiftRightMethodGen .makeFunction(language)); + scope.registerMethod( + bigInteger, + "compare_to", + org.enso.interpreter.node.expression.builtin.number.bigInteger.CompareToMethodGen + .makeFunction(language)); } private void registerDecimalMethods(Language language, ModuleScope scope) { @@ -360,6 +370,11 @@ public class Number { "ceil", org.enso.interpreter.node.expression.builtin.number.decimal.CeilMethodGen.makeFunction( language)); + scope.registerMethod( + decimal, + "compare_to", + org.enso.interpreter.node.expression.builtin.number.decimal.CompareToMethodGen.makeFunction( + language)); } /** @return the Int64 atom constructor. */ diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Ordering.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Ordering.java new file mode 100644 index 0000000000..53079322dd --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Ordering.java @@ -0,0 +1,76 @@ +package org.enso.interpreter.runtime.builtin; + +import org.enso.interpreter.Language; +import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; +import org.enso.interpreter.runtime.scope.ModuleScope; + +/** A container for builtin ordering types. */ +public class Ordering { + private final AtomConstructor ordering; + private final AtomConstructor less; + private final AtomConstructor equal; + private final AtomConstructor greater; + + public Ordering(Language language, ModuleScope scope) { + ordering = new AtomConstructor("Ordering", scope).initializeFields(); + less = new AtomConstructor("Less", scope).initializeFields(); + equal = new AtomConstructor("Equal", scope).initializeFields(); + greater = new AtomConstructor("Greater", scope).initializeFields(); + scope.registerConstructor(ordering); + scope.registerConstructor(less); + scope.registerConstructor(equal); + scope.registerConstructor(greater); + } + + /** + * Convert the java notion of ordering to the Enso notion of ordering. + * + * @param ord the java ordering + * @return the Enso ordering corresponding to {@code ord} + */ + public Atom fromJava(int ord) { + if (ord == 0) { + return newEqual(); + } else if (ord > 0) { + return newGreater(); + } else { + return newLess(); + } + } + + /** @return a new instance of Less */ + public Atom newLess() { + return less.newInstance(); + } + + /** @return a new instance of Equal */ + public Atom newEqual() { + return equal.newInstance(); + } + + /** @return a new instance of Greater */ + public Atom newGreater() { + return greater.newInstance(); + } + + /** @return the Ordering constructor. */ + public AtomConstructor ordering() { + return ordering; + } + + /** @return the Less constructor */ + public AtomConstructor less() { + return less; + } + + /** @return the Equal constructor */ + public AtomConstructor equal() { + return equal; + } + + /** @return the Greater constructor */ + public AtomConstructor greater() { + return greater; + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java index 3752b55696..e7c13ec3bf 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java @@ -59,10 +59,15 @@ public class Array implements TruffleObject { return items[(int) index]; } + /** @return the size of this array */ + public int length() { + return this.items.length; + } + /** * Exposes the size of this collection through the polyglot API. * - * @return + * @return the size of this array */ @ExportMessage long getArraySize() { diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala index fa373f4680..c99ad1f5e2 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala @@ -702,7 +702,7 @@ class IrToTruffle( runtimeConsOpt.map { atomCons => val any = context.getBuiltins.any - val array = context.getBuiltins.mutable.constructor + val array = context.getBuiltins.mutable.array val bool = context.getBuiltins.bool val number = context.getBuiltins.number val polyglot = context.getBuiltins.polyglot.getPolyglot diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala index 46adfebded..a4f83f3f54 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala @@ -228,7 +228,7 @@ case object SuspendedArguments extends IRPass { case (arg, typ) => arg match { case spec: IR.DefinitionArgument.Specified => - if (representsSuspended(typ)) { + if (representsSuspended(typ) || spec.suspended) { spec.copy(suspended = true) } else spec.copy(suspended = false) } diff --git a/std-bits/src/main/java/org/enso/base/Time_Utils.java b/std-bits/src/main/java/org/enso/base/Time_Utils.java index a11dd887e8..3db546513b 100644 --- a/std-bits/src/main/java/org/enso/base/Time_Utils.java +++ b/std-bits/src/main/java/org/enso/base/Time_Utils.java @@ -8,6 +8,7 @@ import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.temporal.TemporalAccessor; import java.util.Locale; +import java.util.Random; /** Utils for standard library operations on Time. */ public class Time_Utils { diff --git a/test/Benchmarks/src/Vector.enso b/test/Benchmarks/src/Vector.enso new file mode 100644 index 0000000000..62e96a169b --- /dev/null +++ b/test/Benchmarks/src/Vector.enso @@ -0,0 +1,73 @@ +from Base import all + +import Test.Bench + +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].to_array + 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].to_array + 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 + diff --git a/test/Tests/src/Data/Noise_Spec.enso b/test/Tests/src/Data/Noise_Spec.enso index f99aff3110..22cb9d7ac1 100644 --- a/test/Tests/src/Data/Noise_Spec.enso +++ b/test/Tests/src/Data/Noise_Spec.enso @@ -14,6 +14,6 @@ spec = describe "Noise" <| result-result . should_equal 0 it "should allow the user to specify the interval" <| interval = Interval.inclusive -250 250 - values = 1.up_to 10000 . to_vector . map (_.noise interval) + values = 1.up_to 10001 . to_vector . map (_.noise interval) values.all (v -> (v >= -250) && (v <= 250)) . should_be_true diff --git a/test/Tests/src/Data/Vector_Spec.enso b/test/Tests/src/Data/Vector_Spec.enso index 7af4059e62..104b1aa392 100644 --- a/test/Tests/src/Data/Vector_Spec.enso +++ b/test/Tests/src/Data/Vector_Spec.enso @@ -2,6 +2,12 @@ from Base import all import Test +type Test a b + +Test.== that = this.a == that.a +Test.compare_to that = if this == that then Ordering.Equal else + if this.a > that.a then Ordering.Greater else Ordering.Less + spec = describe "Vectors" <| it "should allow vector creation with a programmatic constructor" <| Vector.new 100 (ix -> ix + 1) . fold 0 (+) . should_equal 5050 @@ -115,9 +121,38 @@ spec = describe "Vectors" <| non_empty_vec.rest . should_equal [2, 3, 4, 5] singleton_vec.rest . should_equal [] empty_vec.rest . should_equal Nothing - # it "should be able to be sorted" <| - # it "should have a stable sort" <| - # it "should be able to use a custom element projection" <| - # it "should be able to use a custom comparator" <| - # it "should be able to sort in ascending and descending order" <| - + it "should be able to be sorted" <| + empty_vec = [] + short_vec = [2, 4, 38, -1, -1000, 3671, -32] + short_expected = [-1000, -32, -1, 2, 4, 38, 3671] + empty_vec.sort . should_equal [] + short_vec.sort . should_equal short_expected + it "should leave the original vector unchanged" <| + non_empty_vec = [2, 4, 2, 3, 2, 3] + sorted = non_empty_vec.sort + non_empty_vec . should_equal [2, 4, 2, 3, 2, 3] + sorted . should_equal [2, 2, 2, 3, 3, 4] + it "should have a stable sort" <| + small_vec = [Test 1 8, Test 1 3, Test -20 0, Test -1 1, Test -1 10, Test 4 0] + small_expected = [Test -20 0, Test -1 1, Test -1 10, Test 1 8, Test 1 3, Test 4 0] + small_vec.sort . should_equal small_expected + it "should be able to use a custom element projection" <| + small_vec = [Test 1 8, Test 1 3, Test -20 0, Test -1 1, Test -1 10, Test 4 0] + small_expected = [Test -20 0, Test 4 0, Test -1 1, Test 1 3, Test 1 8, Test -1 10] + small_vec.sort (on = _.b) . should_equal small_expected + it "should be able to use a custom comparator" <| + small_vec = [2, 7, -3, 383, -392, 28, -90] + small_expected = [383, 28, 7, 2, -3, -90, -392] + small_vec.sort (by = l -> r -> r.compare_to l) . should_equal small_expected + it "should be able to use a custom comparator and projection" <| + small_vec = [Test 1 8, Test 1 3, Test -20 0, Test -1 1, Test -1 10, Test 4 0] + small_expected = [Test -1 10, Test 1 8, Test 1 3, Test -1 1, Test -20 0, Test 4 0] + small_vec.sort (on = _.b) (by = l -> r -> r.compare_to l) . should_equal small_expected + it "should be able to sort in descending order" <| + small_vec = [2, 7, -3, 383, -392, 28, -90] + small_expected = [383, 28, 7, 2, -3, -90, -392] + small_vec.sort order=Sort_Order.Descending . should_equal small_expected + it "should be stable in descending order" <| + small_vec = [Test 1 8, Test 1 3, Test -20 0, Test -1 1, Test -1 10, Test 4 0] + small_expected = [Test 4 0, Test 1 3, Test 1 8, Test -1 10, Test -1 1, Test -20 0] + small_vec.sort order=Sort_Order.Descending . should_equal small_expected