Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Map Implementation #1222

Merged
merged 10 commits into from
Oct 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion distribution/bin/enso
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
COMP_PATH=$(dirname "$0")/../component
exec java -jar -Dtruffle.class.path.append="$COMP_PATH/runtime.jar" -Dpolyglot.engine.IterativePartialEscape=true $JAVA_OPTS $COMP_PATH/runner.jar "$@"
exec java -jar -Dtruffle.class.path.append="$COMP_PATH/runtime.jar" $JAVA_OPTS $COMP_PATH/runner.jar "$@"
exit
79 changes: 79 additions & 0 deletions distribution/std-lib/Base/src/Data/Map.enso
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from Base import all
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan of where this file is. I think we should unify the collections under Base.Collection (including List and Vector). Can we talk about stdlib structure?

import Base.Data.Map.Internal

## An error for getting a missing value from a map.
type No_Value_For_Key key

## A key-value store. This type assumes all keys are pairwise comparable,
using the `<`, `>` and `==` operators.
type Map
type Tip
type Bin s key value left right

## Checks if the map is empty.
is_empty : Boolean
is_empty = case this of
Bin _ _ _ _ _ -> False
Tip -> True

## Returns the number of entries in this map.
size : Integer
size = case this of
Bin s _ _ _ _ -> s
Tip -> 0

## Converts the map into a vector of `[key, value]` pairs.
The returned vector is sorted in the increasing order of keys.
to_vector : Vector
to_vector =
builder = Vector.new_builder
to_vector_with_builder m = case m of
Bin _ k v l r ->
to_vector_with_builder l
builder.append [k, v]
to_vector_with_builder r
Unit
Tip -> Unit
to_vector_with_builder this
result = builder.to_vector
result

## Returns a text representation of this map.
to_text : Text
to_text = this.to_vector.to_text

## Checks if this map is equal to another map.

Maps are equal when they contained the same keys and the values
associated with each key are pairwise equal.
== : Map -> Boolean
== that = this.to_vector == that.to_vector

## Maps a function over each value in this map.
map : (Any -> Any) -> Map
map function = case this of
Bin s k v l r ->
Bin s k (function v) (l.map function) (r.map function)
Tip -> Tip

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see a fold (left) as well.

## Gets the value associated with `key` in this map, or returns a
`No_Value_For_Key` error, if `key` is not present.
get : Any -> Any ! No_Value_For_Key
get key =
Comment on lines +61 to +62
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not want a more general Maybe a here, rather than the specific error? It seems more composable to me to use a common primitive.

go map = case map of
Tip -> Error.throw (No_Value_For_Key key)
Bin _ k v l r ->
if k == key then v else
if k > key then @Tail_Call go l else @Tail_Call go r
result = go this
result

## Inserts a key-value mapping into this map. If `key` is already present,
it will be overriden with the new `value`.
insert : Any -> Any -> Map
insert key value = Internal.insert this key value

## Returns an empty map.
empty : Map
empty = Tip

113 changes: 113 additions & 0 deletions distribution/std-lib/Base/src/Data/Map/Internal.enso
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from Base import all
from Base.Data.Map import all

## PRIVATE

Helper used in the insert operation.
insert_l key value k v l r =
new_left = here.insert l key value
here.balance_left k v new_left r

## PRIVATE

Helper used in the insert operation.
insert_r key value k v l r =
new_right = here.insert r key value
here.balance_right k v l new_right

## PRIVATE

Helper for inserting a new key-value pair into a map.

The algorithm used here is based on the paper "Implementing Sets Efficiently
in a Functional Language" by Stephen Adams.
Implementation is based on Haskell's `Data.Map.Strict` implemented in the
`containers` package.
insert map key value = case map of
Bin s k v l r ->
if key > k then @Tail_Call here.insert_r key value k v l r else
if key == k then @Tail_Call Bin s k value l r else
@Tail_Call here.insert_l key value k v l r
_ -> Bin 1 key value Tip Tip

## PRIVATE

Rebalances the map after the left subtree grows.
balance_left k x l r = case r of
Bin rs _ _ _ _ -> case l of
Bin ls lk lx ll lr ->
if ls <= Delta*rs then Bin 1+ls+rs k x l r else
lls = here.size ll
case lr of
Bin lrs lrk lrx lrl lrr ->
if lrs < Ratio*lls then Bin 1+ls+rs lk lx ll (Bin 1+rs+lrs k x lr r) else
lrls = here.size lrl
lrrs = here.size lrr
Bin 1+ls+rs lrk lrx (Bin 1+lls+lrls lk lx ll lrl) (Bin 1+rs+lrrs k x lrr r)
_ -> Bin 1+rs k x Tip r
_ -> case l of
Tip -> Bin 1 k x Tip Tip
Bin _ _ _ Tip Tip -> Bin 2 k x l Tip
Bin _ lk lx Tip (Bin _ lrk lrx _ _) -> Bin 3 lrk lrx (Bin 1 lk lx Tip Tip) (Bin 1 k x Tip Tip)
Bin _ lk lx ll Tip -> Bin 3 lk lx ll (Bin 1 k x Tip Tip)
Bin ls lk lx ll lr -> case lr of
Bin lrs lrk lrx lrl lrr ->
lls = here.size ll
if lrs < Ratio*lls then Bin 1+ls lk lx ll (Bin 1+lrs k x lr Tip) else
lrls = here.size lrl
lrrs = here.size lrr
Bin 1+ls lrk lrx (Bin 1+lls+lrls lk lx ll lrl) (Bin 1+lrrs k x lrr Tip)

## PRIVATE

Rebalances the map after the right subtree grows.
balance_right k x l r = case l of
Bin ls _ _ _ _ -> case r of
Bin rs rk rx rl rr ->
if rs <= Delta*ls then Bin 1+ls+rs k x l r else
case rl of
Bin rls rlk rlx rll rlr ->
rrs = here.size rr
if rls < Ratio*rrs then Bin 1+ls+rs rk rx (Bin 1+ls+rls k x l rl) rr else
rlls = here.size rll
rlrs = here.size rlr
Bin 1+ls+rs rlk rlx (Bin 1+ls+rlls k x l rll) (Bin 1+rrs+rlrs rk rx rlr rr)
_ -> Bin 1+ls k x l Tip
_ -> case r of
Tip -> Bin 1 k x Tip Tip
Bin _ _ _ Tip Tip -> Bin 2 k x Tip r
Bin _ rk rx Tip rr -> Bin 3 rk rx (Bin 1 k x Tip Tip) rr
Bin _ rk rx (Bin _ rlk rlx _ _) Tip -> Bin 3 rlk rlx (Bin 1 k x Tip Tip) (Bin 1 rk rx Tip Tip)
Bin rs rk rx (Bin rls rlk rlx rll rlr) rr -> case rr of
Bin rrs _ _ _ _ ->
if rls < Ratio*rrs then Bin 1+rs rk rx (Bin 1+rls k x Tip rl) rr else
srll = here.size rll
srlr = here.size rlr
Bin 1+rs rlk rlx (Bin 1+srll k x Tip rll) (Bin 1+rrs+srlr rk rx rlr rr)

## PRIVATE

Controls the difference between inner and outer siblings of a heavy subtree.
Used to decide between a double and a single rotation.

The choice of values for `ratio` and `delta` is taken from the Haskell
implementation.
ratio : Integer
ratio = 2

## PRIVATE

Controls the maximum size difference between subtrees.

The choice of values for `ratio` and `delta` is taken from the Haskell
implementation.
delta : Integer
delta = 3

## PRIVATE

Gets the size of a map.
size m = case m of
Bin s _ _ _ _ -> s
_ -> 0

2 changes: 2 additions & 0 deletions distribution/std-lib/Base/src/Main.enso
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import Base.Meta.Enso_Project
import Base.Meta.Meta
import Base.Error.Extensions
import Base.Polyglot.Java
import Base.Data.Map
from Builtins import Unit, Number, Integer, Any, True, False, Cons

export Base.Meta.Meta
from Builtins export all hiding Meta

export Base.Data.Map
from Base.Meta.Enso_Project export all
from Base.List export Nil, Cons
from Base.Vector export Vector
Expand Down
2 changes: 1 addition & 1 deletion distribution/std-lib/Base/src/Test.enso
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type Verbs

equal subject argument =
if subject == argument then Success else
msg = this.to_text + " did not equal " + that.to_text + "."
msg = this.to_text + " did not equal " + argument.to_text + "."
here.fail msg

be subject argument = this.equal subject argument
Expand Down
9 changes: 9 additions & 0 deletions distribution/std-lib/Base/src/Text/Extensions.enso
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ Text.split_at separator =
Text.== : Text -> Boolean
Text.== that = Text_Utils.equals [this, that]

## Checks if `this` is lexicographically before `that`.
Text.< : Text -> Boolean
Text.< that = Text_Utils.lt [this, that]

## Checks if `this` is lexicographically after `that`.
Text.> : Text -> Boolean
Text.> that = Text_Utils.lt [that, this]
Comment on lines +72 to +78
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure lexicographic order is particularly intuitive to a non-programmer. It produces counterintuitive orderings such as "One" < "abc" just due to capitalisation.


## Returns a vector containing bytes representing the UTF-8 encoding of the
input text.

Expand Down Expand Up @@ -103,4 +111,5 @@ Text.from_codepoints : Vector -> Text
Text.from_codepoints codepoints = Text_Utils.from_codepoints [codepoints.to_array]

## Checks whether `this` starts with `prefix`.
Text.starts_with : Text -> Boolean
Text.starts_with prefix = Text_Utils.starts_with [this, prefix]
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
package org.enso.interpreter.bench.benchmarks.semantic;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.TimeUnit;
import org.enso.interpreter.bench.fixtures.semantic.RecursionFixtures;
import org.enso.interpreter.test.DefaultInterpreterRunner;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.annotations.*;

import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime)
@Fork(1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public Stateful invokeDynamicSymbol(
Object selfArgument = arguments[thisArgumentPosition];
if (argumentsExecutionMode.shouldExecute()) {
Stateful selfResult =
thisExecutor.executeThunk((Thunk) selfArgument, state, BaseNode.TailStatus.NOT_TAIL);
thisExecutor.executeThunk(selfArgument, state, BaseNode.TailStatus.NOT_TAIL);
selfArgument = selfResult.getValue();
state = selfResult.getState();
arguments[thisArgumentPosition] = selfArgument;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,7 @@ public Stateful invokeDynamicSymbol(
lock.unlock();
}
}
Stateful selfResult =
thisExecutor.executeThunk((Thunk) selfArgument, state, TailStatus.NOT_TAIL);
Stateful selfResult = thisExecutor.executeThunk(selfArgument, state, TailStatus.NOT_TAIL);
selfArgument = selfResult.getValue();
state = selfResult.getState();
arguments[thisArgumentPosition] = selfArgument;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ public static ArgumentSorterNode build(
preApplicationSchema, mapping.getPostApplicationSchema(), mapping, argumentsExecutionMode);
}

private void initArgumentExecutors(Object[] arguments) {
private void initArgumentExecutors() {
ThunkExecutorNode[] executors =
new ThunkExecutorNode[mapping.getArgumentShouldExecute().length];
for (int i = 0; i < mapping.getArgumentShouldExecute().length; i++) {
if (mapping.getArgumentShouldExecute()[i] && TypesGen.isThunk(arguments[i])) {
if (mapping.getArgumentShouldExecute()[i]) {
executors[i] = insert(ThunkExecutorNode.build());
}
}
Expand All @@ -74,16 +74,15 @@ private Object executeArguments(Object[] arguments, Object state) {
lock.lock();
try {
if (executors == null) {
initArgumentExecutors(arguments);
initArgumentExecutors();
}
} finally {
lock.unlock();
}
}
for (int i = 0; i < mapping.getArgumentShouldExecute().length; i++) {
if (executors[i] != null) {
Stateful result =
executors[i].executeThunk(TypesGen.asThunk(arguments[i]), state, TailStatus.NOT_TAIL);
Stateful result = executors[i].executeThunk(arguments[i], state, TailStatus.NOT_TAIL);
arguments[i] = result.getValue();
state = result.getState();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static ForceNode build(ExpressionNode target) {
@Specialization
Object passToExecutorNode(
VirtualFrame frame,
Thunk thunk,
Object thunk,
@Cached("build()") ThunkExecutorNode thunkExecutorNode) {
Object state = FrameUtil.getObjectSafe(frame, getStateFrameSlot());
Stateful result = thunkExecutorNode.executeThunk(thunk, state, getTailStatus());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.control.TailCallException;
import org.enso.interpreter.runtime.state.Stateful;
import org.enso.interpreter.runtime.type.TypesGen;

/** Node responsible for executing (forcing) thunks passed to it as runtime values. */
@GenerateUncached
Expand All @@ -37,7 +38,16 @@ public static ThunkExecutorNode build() {
* @param isTail is the execution happening in a tail-call position
* @return the return value of this thunk
*/
public abstract Stateful executeThunk(Thunk thunk, Object state, BaseNode.TailStatus isTail);
public abstract Stateful executeThunk(Object thunk, Object state, BaseNode.TailStatus isTail);

static boolean isThunk(Object th) {
return TypesGen.isThunk(th);
}

@Specialization(guards = "!isThunk(thunk)")
Stateful doOther(Object thunk, Object state, BaseNode.TailStatus isTail) {
return new Stateful(state, thunk);
}

@Specialization(
guards = "callNode.getCallTarget() == thunk.getCallTarget()",
Expand Down
Loading