diff options
author | Louis Pilfold <louis@lpil.uk> | 2021-09-01 22:13:56 +0100 |
---|---|---|
committer | Louis Pilfold <louis@lpil.uk> | 2021-09-01 22:13:56 +0100 |
commit | 165acf00cc9edff603bf200a2b8ce73c6577621d (patch) | |
tree | 4cc349d2939372eb2f54b8b8ebe53ad80079bd40 | |
parent | 2455b836fc97492b58bcda34fa689d5f570a4a21 (diff) | |
download | gleam_stdlib-165acf00cc9edff603bf200a2b8ce73c6577621d.tar.gz gleam_stdlib-165acf00cc9edff603bf200a2b8ce73c6577621d.zip |
Move Map impl to Gleam and a JS class
-rw-r--r-- | src/gleam/io.gleam | 2 | ||||
-rw-r--r-- | src/gleam/map.gleam | 98 | ||||
-rw-r--r-- | src/gleam_stdlib.js | 131 | ||||
-rw-r--r-- | test/gleam/map_test.gleam | 24 |
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() { |