aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLouis Pilfold <louis@lpil.uk>2021-09-01 22:13:56 +0100
committerLouis Pilfold <louis@lpil.uk>2021-09-01 22:13:56 +0100
commit165acf00cc9edff603bf200a2b8ce73c6577621d (patch)
tree4cc349d2939372eb2f54b8b8ebe53ad80079bd40
parent2455b836fc97492b58bcda34fa689d5f570a4a21 (diff)
downloadgleam_stdlib-165acf00cc9edff603bf200a2b8ce73c6577621d.tar.gz
gleam_stdlib-165acf00cc9edff603bf200a2b8ce73c6577621d.zip
Move Map impl to Gleam and a JS class
-rw-r--r--src/gleam/io.gleam2
-rw-r--r--src/gleam/map.gleam98
-rw-r--r--src/gleam_stdlib.js131
-rw-r--r--test/gleam/map_test.gleam24
4 files changed, 152 insertions, 103 deletions
diff --git a/src/gleam/io.gleam b/src/gleam/io.gleam
index baa0bdb..3139e73 100644
--- a/src/gleam/io.gleam
+++ b/src/gleam/io.gleam
@@ -85,7 +85,7 @@ if erlang {
if javascript {
external fn debug_print(anything) -> Nil =
- "../gleam_stdlib.js" "log"
+ "../gleam_stdlib.js" "debug"
}
if erlang {
diff --git a/src/gleam/map.gleam b/src/gleam/map.gleam
index 2cb858b..8e423cc 100644
--- a/src/gleam/map.gleam
+++ b/src/gleam/map.gleam
@@ -1,6 +1,7 @@
import gleam/result
import gleam/option.{Option}
import gleam/list
+import gleam/pair
/// A dictionary of keys and values.
///
@@ -76,7 +77,7 @@ if javascript {
/// If two tuples have the same key the last one in the list will be the one
/// that is present in the map.
///
-pub fn from_list(list: List(#(key, value))) -> Map(key, value) {
+pub fn from_list(list: List(#(k, v))) -> Map(k, v) {
do_from_list(list)
}
@@ -86,8 +87,13 @@ if erlang {
}
if javascript {
- external fn do_from_list(List(#(key, value))) -> Map(key, value) =
- "../gleam_stdlib.js" "map_from_list"
+ fn do_from_list(list: List(#(k, v))) -> Map(k, v) {
+ list.fold(list, new(), insert_pair)
+ }
+}
+
+fn insert_pair(pair: #(k, v), map: Map(k, v)) -> Map(k, v) {
+ insert(map, pair.0, pair.1)
}
/// Determines whether or not a value present in the map for a given key.
@@ -110,8 +116,9 @@ if erlang {
}
if javascript {
- external fn do_has_key(k, Map(k, v)) -> Bool =
- "../gleam_stdlib.js" "map_has_key"
+ fn do_has_key(key: k, map: Map(k, v)) -> Bool {
+ get(map, key) != Error(Nil)
+ }
}
/// Creates a fresh map that contains no values.
@@ -144,7 +151,7 @@ if javascript {
/// Error(Nil)
///
pub fn get(from: Map(key, value), get: key) -> Result(value, Nil) {
- do_get(from, get);
+ do_get(from, get)
}
if erlang {
@@ -205,8 +212,15 @@ if erlang {
}
if javascript {
- external fn do_map_values(fn(key, value) -> b, Map(key, value)) -> Map(key, b) =
- "../gleam_stdlib.js" "map_map_values"
+ fn do_map_values(f: fn(key, value) -> b, map: Map(key, value)) -> Map(key, b) {
+ let insert = fn(pair, map) {
+ let #(k, v) = pair
+ insert(map, k, f(k, v))
+ }
+ map
+ |> to_list
+ |> list.fold(new(), insert)
+ }
}
/// Gets a list of all keys in a given map.
@@ -230,8 +244,11 @@ if erlang {
}
if javascript {
- external fn do_keys(Map(keys, v)) -> List(keys) =
- "../gleam_stdlib.js" "map_keys"
+ fn do_keys(map: Map(k, v)) -> List(k) {
+ map
+ |> to_list
+ |> list.map(pair.first)
+ }
}
/// Gets a list of all values in a given map.
@@ -255,8 +272,11 @@ if erlang {
}
if javascript {
- external fn do_values(Map(k, values)) -> List(values) =
- "../gleam_stdlib.js" "map_values"
+ fn do_values(map: Map(k, v)) -> List(v) {
+ map
+ |> to_list
+ |> list.map(pair.second)
+ }
}
/// Creates a new map from a given map, minus any entries that a given function
@@ -285,11 +305,21 @@ if erlang {
}
if javascript {
- external fn do_filter(
- fn(key, value) -> Bool,
- Map(key, value),
- ) -> Map(key, value) =
- "../gleam_stdlib.js" "map_filter"
+ fn do_filter(
+ f: fn(key, value) -> Bool,
+ map: Map(key, value),
+ ) -> Map(key, value) {
+ let insert = fn(pair, map) {
+ let #(k, v) = pair
+ case f(k, v) {
+ True -> insert(map, k, v)
+ _ -> map
+ }
+ }
+ map
+ |> to_list
+ |> list.fold(new(), insert)
+ }
}
/// Creates a new map from a given map, only including any entries for which the
@@ -310,13 +340,20 @@ pub fn take(from map: Map(k, v), keeping desired_keys: List(k)) -> Map(k, v) {
}
if erlang {
- external fn do_take(List(k), Map(k, v)) -> Map(k, v) =
- "maps" "with"
+ external fn do_take(List(k), Map(k, v)) -> Map(k, v) =
+ "maps" "with"
}
if javascript {
- external fn do_take(List(k), Map(k, v)) -> Map(k, v) =
- "../gleam_stdlib.js" "map_take"
+ fn do_take(desired_keys: List(k), map: Map(k, v)) -> Map(k, v) {
+ let insert = fn(key, taken) {
+ case get(map, key) {
+ Ok(value) -> insert(taken, key, value)
+ _ -> taken
+ }
+ }
+ list.fold(desired_keys, new(), insert)
+ }
}
/// Creates a new map from a pair of given maps by combining their entries.
@@ -331,18 +368,21 @@ if javascript {
/// > merge(a, b)
/// from_list([#("a", 0), #("b", 2), #("c", 3)])
///
-pub fn merge(into: Map(k, v), merge: Map(k, v)) -> Map(k, v) {
- do_merge(into, merge)
+pub fn merge(into map: Map(k, v), from new_entries: Map(k, v)) -> Map(k, v) {
+ do_merge(map, new_entries)
}
if erlang {
- external fn do_merge(into: Map(k, v), merge: Map(k, v)) -> Map(k, v) =
+ external fn do_merge(Map(k, v), Map(k, v)) -> Map(k, v) =
"maps" "merge"
}
if javascript {
- external fn do_merge(into: Map(k, v), merge: Map(k, v)) -> Map(k, v) =
- "../gleam_stdlib.js" "map_merge"
+ fn do_merge(map: Map(k, v), new_entries: Map(k, v)) -> Map(k, v) {
+ new_entries
+ |> to_list
+ |> list.fold(map, insert_pair)
+ }
}
/// Creates a new map from a given map with all the same entries except for the
@@ -421,11 +461,7 @@ pub fn update(
|> insert(map, key, _)
}
-fn do_fold(
- list: List(#(k, v)),
- initial: acc,
- fun: fn(k, v, acc) -> acc,
-) -> acc {
+fn do_fold(list: List(#(k, v)), initial: acc, fun: fn(k, v, acc) -> acc) -> acc {
case list {
[] -> initial
[#(k, v), ..tail] -> do_fold(tail, fun(k, v, initial), fun)
diff --git a/src/gleam_stdlib.js b/src/gleam_stdlib.js
index 64b1aeb..360de75 100644
--- a/src/gleam_stdlib.js
+++ b/src/gleam_stdlib.js
@@ -6,6 +6,7 @@ import {
UtfCodepoint,
toBitString,
stringBits,
+ inspect,
} from "./gleam.js";
import {
CompileError as RegexCompileError,
@@ -183,6 +184,10 @@ export function log(term) {
console.log(term);
}
+export function debug(term) {
+ console.log(inspect(term));
+}
+
export function crash(message) {
throw new globalThis.Error(message);
}
@@ -261,91 +266,89 @@ export function regex_scan(regex, string) {
return List.fromArray(matches);
}
-export function hashcode(obj) {
- let existing = HASHCODE_CACHE.get(obj);
- if (existing) {
- return existing;
- } else if (obj instanceof Object) {
- let hashcode = JSON.stringify(obj);
- HASHCODE_CACHE.set(obj, hashcode);
- return hashcode;
- } else {
- return obj.toString();
+class Map {
+ static #hashcode_cache = new WeakMap();
+
+ static hash(value) {
+ let existing = this.#hashcode_cache.get(value);
+ if (existing) {
+ return existing;
+ } else if (value instanceof Object) {
+ let hashcode = JSON.stringify(value);
+ HASHCODE_CACHE.set(value, hashcode);
+ return hashcode;
+ } else {
+ return value.toString();
+ }
}
-}
-export function new_map() {
- return new Map();
-}
+ constructor() {
+ this.entries = new globalThis.Map();
+ }
-export function map_size(map) {
- return map.size;
-}
+ get size() {
+ return this.entries.size;
+ }
-export function map_to_list(map) {
- return List.fromArray([...map.values()]);
-}
+ inspect() {
+ let entries = [...this.entries.values()]
+ .map((pair) => inspect(pair))
+ .join(", ");
+ return `map.from_list([${entries}])`;
+ }
-export function map_from_list(list) {
- let map = new Map();
- for (let pair of list) map.set(hashcode(pair[0]), pair);
- return map;
-}
+ copy() {
+ let map = new Map();
+ map.entries = new globalThis.Map(this.entries);
+ return map;
+ }
-export function map_has_key(k, map) {
- return map.has(hashcode(k));
-}
+ toList() {
+ return List.fromArray([...this.entries.values()]);
+ }
-export function map_remove(k, map) {
- const result = new Map(map);
- result.delete(hashcode(k));
- return result;
-}
+ insert(k, v) {
+ let map = this.copy();
+ map.entries.set(Map.hash(k), [k, v]);
+ return map;
+ }
-export function map_filter(f, map) {
- const result = new Map();
- for (let [hash, [k, v]] of map) {
- if (f(k)) result.set(hash, [k, v]);
+ delete(k) {
+ let map = this.copy();
+ map.entries.delete(Map.hash(k));
+ return map;
}
- return result;
-}
-export function map_get(from, get) {
- const entry = from.get(hashcode(get));
- if (entry) {
- return new Ok(entry[1]);
- } else {
- return new Error(Nil);
+ get(key) {
+ let code = Map.hash(key);
+ if (this.entries.has(code)) {
+ return new Ok(this.entries.get(code)[1]);
+ } else {
+ return new Error(Nil);
+ }
}
}
-export function map_insert(key, value, map) {
- return new Map(map).set(hashcode(key), [key, value]);
+export function new_map() {
+ return new Map();
}
-export function map_keys(map) {
- return List.fromArray([...map.values()].map(([key, value]) => key));
+export function map_size(map) {
+ return map.size;
}
-export function map_values(map) {
- return List.fromArray([...map.values()].map(([key, value]) => value));
+export function map_to_list(map) {
+ return map.toList();
}
-export function map_map_values(fn, map) {
- const result = new Map();
- for (let [hash, [k, v]] of map) result.set(hash, [k, fn(k, v)]);
- return result;
+export function map_remove(k, map) {
+ return map.delete(k);
}
-export function map_merge(into, merge) {
- return new Map([...into, ...merge]);
+export function map_get(map, key) {
+ return map.get(key);
}
-export function map_take(keys, map) {
- const result = new Map();
- for (let key of keys) {
- const code = hashcode(key);
- if (map.has(code)) result.set(code, map.get(code));
- }
- return result;
+export function map_insert(key, value, map) {
+ return map.insert(key, value);
}
diff --git a/test/gleam/map_test.gleam b/test/gleam/map_test.gleam
index 449509a..fc90b93 100644
--- a/test/gleam/map_test.gleam
+++ b/test/gleam/map_test.gleam
@@ -106,9 +106,19 @@ pub fn drop_test() {
|> should.equal(map.from_list([#("c", 2)]))
}
+pub fn merge_same_key_test() {
+ let a = map.from_list([#("a", 2)])
+ let b = map.from_list([#("a", 0)])
+
+ map.merge(a, b)
+ |> should.equal(map.from_list([#("a", 0)]))
+
+ map.merge(b, a)
+ |> should.equal(map.from_list([#("a", 2)]))
+}
+
pub fn merge_test() {
let a = map.from_list([#("a", 2), #("c", 4), #("d", 3)])
-
let b = map.from_list([#("a", 0), #("b", 1), #("c", 2)])
map.merge(a, b)
@@ -140,13 +150,13 @@ pub fn update_test() {
|> map.update("a", inc_or_zero)
|> should.equal(map.from_list([#("a", 1), #("b", 1), #("c", 2)]))
- // dict
- // |> map.update("b", inc_or_zero)
- // |> should.equal(map.from_list([#("a", 0), #("b", 2), #("c", 2)]))
+ dict
+ |> map.update("b", inc_or_zero)
+ |> should.equal(map.from_list([#("a", 0), #("b", 2), #("c", 2)]))
- // dict
- // |> map.update("z", inc_or_zero)
- // |> should.equal(map.from_list([#("a", 0), #("b", 1), #("c", 2), #("z", 0)]))
+ dict
+ |> map.update("z", inc_or_zero)
+ |> should.equal(map.from_list([#("a", 0), #("b", 1), #("c", 2), #("z", 0)]))
}
pub fn fold_test() {