From 8759cfbb56cf43e12619f4ca86f3e9e9fa27a309 Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Sat, 24 May 2025 17:26:05 +0400 Subject: [PATCH 1/8] Add String.isEmpty and String.capitalize --- lib/es6/Stdlib_String.js | 14 ++++++++++++++ lib/js/Stdlib_String.js | 14 ++++++++++++++ runtime/Stdlib_String.res | 9 +++++++++ runtime/Stdlib_String.resi | 30 ++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+) diff --git a/lib/es6/Stdlib_String.js b/lib/es6/Stdlib_String.js index d68111d2a3..b6e5069cab 100644 --- a/lib/es6/Stdlib_String.js +++ b/lib/es6/Stdlib_String.js @@ -25,9 +25,23 @@ function searchOpt(s, re) { } +function isEmpty(s) { + return s.length === 0; +} + +function capitalize(s) { + if (s.length === 0) { + return s; + } else { + return s[0].toUpperCase() + s.slice(1); + } +} + export { indexOfOpt, lastIndexOfOpt, searchOpt, + isEmpty, + capitalize, } /* No side effect */ diff --git a/lib/js/Stdlib_String.js b/lib/js/Stdlib_String.js index b653c67e12..09ffc8b903 100644 --- a/lib/js/Stdlib_String.js +++ b/lib/js/Stdlib_String.js @@ -25,7 +25,21 @@ function searchOpt(s, re) { } +function isEmpty(s) { + return s.length === 0; +} + +function capitalize(s) { + if (s.length === 0) { + return s; + } else { + return s[0].toUpperCase() + s.slice(1); + } +} + exports.indexOfOpt = indexOfOpt; exports.lastIndexOfOpt = lastIndexOfOpt; exports.searchOpt = searchOpt; +exports.isEmpty = isEmpty; +exports.capitalize = capitalize; /* No side effect */ diff --git a/runtime/Stdlib_String.res b/runtime/Stdlib_String.res index a1ca7605f8..e0a3f9cbc3 100644 --- a/runtime/Stdlib_String.res +++ b/runtime/Stdlib_String.res @@ -168,6 +168,15 @@ external substringToEnd: (string, ~start: int) => string = "substring" @send external localeCompare: (string, string) => float = "localeCompare" +let isEmpty = s => length(s) == 0 + +let capitalize = s => + if isEmpty(s) { + s + } else { + toUpperCase(getUnsafe(s, 0)) ++ sliceToEnd(s, ~start=1) + } + external ignore: string => unit = "%ignore" @get_index external getSymbolUnsafe: (string, Stdlib_Symbol.t) => 'a = "" diff --git a/runtime/Stdlib_String.resi b/runtime/Stdlib_String.resi index 18a616525b..d72631880f 100644 --- a/runtime/Stdlib_String.resi +++ b/runtime/Stdlib_String.resi @@ -808,6 +808,36 @@ String.searchOpt("no numbers", /\d+/) == None */ let searchOpt: (string, Stdlib_RegExp.t) => option +/** +`isEmpty(str)` returns `true` if the string is empty (has zero length), +`false` otherwise. + +## Examples + +```rescript +String.isEmpty("") == true +String.isEmpty("hello") == false +String.isEmpty(" ") == true +``` +*/ +let isEmpty: string => bool + +/** +`capitalize(str)` returns a new string with the first character converted to +uppercase and the remaining characters unchanged. If the string is empty, +returns the empty string. + +## Examples + +```rescript +String.capitalize("hello") == "Hello" +String.capitalize("HELLO") == "HELLO" +String.capitalize("hello world") == "Hello world" +String.capitalize("") == "" +``` +*/ +let capitalize: string => string + /** `slice(str, ~start, ~end)` returns the substring of `str` starting at character `start` up to but not including `end`. From 593af93e5d563fad41862f56ec2b9a9ba709f40f Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Sat, 24 May 2025 17:35:32 +0400 Subject: [PATCH 2/8] Fix String.isEmpty doc string --- runtime/Stdlib_String.resi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/Stdlib_String.resi b/runtime/Stdlib_String.resi index d72631880f..b0b76dce38 100644 --- a/runtime/Stdlib_String.resi +++ b/runtime/Stdlib_String.resi @@ -817,7 +817,7 @@ let searchOpt: (string, Stdlib_RegExp.t) => option ```rescript String.isEmpty("") == true String.isEmpty("hello") == false -String.isEmpty(" ") == true +String.isEmpty(" ") == false ``` */ let isEmpty: string => bool From 2a1232831a726541835a351b7d36f2d2ad8a6ef5 Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Sat, 24 May 2025 17:35:42 +0400 Subject: [PATCH 3/8] Add Dict.size --- lib/es6/Stdlib_Dict.js | 5 +++++ lib/js/Stdlib_Dict.js | 5 +++++ runtime/Stdlib_Dict.res | 2 ++ runtime/Stdlib_Dict.resi | 19 +++++++++++++++++++ 4 files changed, 31 insertions(+) diff --git a/lib/es6/Stdlib_Dict.js b/lib/es6/Stdlib_Dict.js index 51a70b3144..3142ce1031 100644 --- a/lib/es6/Stdlib_Dict.js +++ b/lib/es6/Stdlib_Dict.js @@ -5,6 +5,10 @@ function $$delete$1(dict, string) { delete(dict[string]); } +function size(dict) { + return Object.keys(dict).length; +} + function forEach(dict, f) { Object.values(dict).forEach(value => f(value)); } @@ -24,6 +28,7 @@ function mapValues(dict, f) { export { $$delete$1 as $$delete, + size, forEach, forEachWithKey, mapValues, diff --git a/lib/js/Stdlib_Dict.js b/lib/js/Stdlib_Dict.js index ecb9c90844..107ab353c9 100644 --- a/lib/js/Stdlib_Dict.js +++ b/lib/js/Stdlib_Dict.js @@ -5,6 +5,10 @@ function $$delete$1(dict, string) { delete(dict[string]); } +function size(dict) { + return Object.keys(dict).length; +} + function forEach(dict, f) { Object.values(dict).forEach(value => f(value)); } @@ -23,6 +27,7 @@ function mapValues(dict, f) { } exports.$$delete = $$delete$1; +exports.size = size; exports.forEach = forEach; exports.forEachWithKey = forEachWithKey; exports.mapValues = mapValues; diff --git a/runtime/Stdlib_Dict.res b/runtime/Stdlib_Dict.res index 7604962689..5adb4f0667 100644 --- a/runtime/Stdlib_Dict.res +++ b/runtime/Stdlib_Dict.res @@ -18,6 +18,8 @@ let delete = (dict, string) => { @val external keysToArray: dict<'a> => array = "Object.keys" +let size = dict => dict->keysToArray->Stdlib_Array.length + @val external valuesToArray: dict<'a> => array<'a> = "Object.values" @val external assign: (dict<'a>, dict<'a>) => dict<'a> = "Object.assign" diff --git a/runtime/Stdlib_Dict.resi b/runtime/Stdlib_Dict.resi index e85f05441c..f3e9a24abb 100644 --- a/runtime/Stdlib_Dict.resi +++ b/runtime/Stdlib_Dict.resi @@ -144,6 +144,25 @@ Console.log(keys) // Logs `["someKey", "someKey2"]` to the console @val external keysToArray: dict<'a> => array = "Object.keys" +/** +`size(dictionary)` returns the number of key/value pairs in the dictionary. + +## Examples +```rescript +let dict = Dict.make() +dict->Dict.size->assertEqual(0) + +dict->Dict.set("someKey", 1) +dict->Dict.set("someKey2", 2) +dict->Dict.size->assertEqual(2) + +// After deleting a key +dict->Dict.delete("someKey") +dict->Dict.size->assertEqual(1) +``` +*/ +let size: dict<'a> => int + /** `valuesToArray(dictionary)` returns an array of all the values of the dictionary. From d2099af5b14b058ff100ad6821cc9503fb6e5c5b Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Sat, 24 May 2025 18:25:15 +0400 Subject: [PATCH 4/8] Add `isEmpty` function to Array, Dict, Map, Set with corresponding documentation --- lib/es6/Stdlib_Array.js | 5 +++++ lib/es6/Stdlib_Dict.js | 5 +++++ lib/es6/Stdlib_Map.js | 12 +++++++++++- lib/es6/Stdlib_Set.js | 12 +++++++++++- lib/js/Stdlib_Array.js | 5 +++++ lib/js/Stdlib_Dict.js | 5 +++++ lib/js/Stdlib_Map.js | 10 +++++++++- lib/js/Stdlib_Set.js | 10 +++++++++- runtime/Stdlib_Array.res | 2 ++ runtime/Stdlib_Array.resi | 22 ++++++++++++++++++++-- runtime/Stdlib_Dict.res | 2 ++ runtime/Stdlib_Dict.resi | 19 +++++++++++++++++++ runtime/Stdlib_Map.res | 2 ++ runtime/Stdlib_Map.resi | 19 +++++++++++++++++++ runtime/Stdlib_Set.res | 2 ++ runtime/Stdlib_Set.resi | 19 +++++++++++++++++++ 16 files changed, 145 insertions(+), 6 deletions(-) diff --git a/lib/es6/Stdlib_Array.js b/lib/es6/Stdlib_Array.js index b1bbc32728..f733d81a00 100644 --- a/lib/es6/Stdlib_Array.js +++ b/lib/es6/Stdlib_Array.js @@ -22,6 +22,10 @@ function fromInitializer(length, f) { return arr; } +function isEmpty(arr) { + return arr.length === 0; +} + function equal(a, b, eq) { let len = a.length; if (len === b.length) { @@ -183,6 +187,7 @@ export { fromInitializer, equal, compare, + isEmpty, indexOfOpt, lastIndexOfOpt, reduce, diff --git a/lib/es6/Stdlib_Dict.js b/lib/es6/Stdlib_Dict.js index 3142ce1031..6741659742 100644 --- a/lib/es6/Stdlib_Dict.js +++ b/lib/es6/Stdlib_Dict.js @@ -9,6 +9,10 @@ function size(dict) { return Object.keys(dict).length; } +function isEmpty(dict) { + return Object.keys(dict).length === 0; +} + function forEach(dict, f) { Object.values(dict).forEach(value => f(value)); } @@ -29,6 +33,7 @@ function mapValues(dict, f) { export { $$delete$1 as $$delete, size, + isEmpty, forEach, forEachWithKey, mapValues, diff --git a/lib/es6/Stdlib_Map.js b/lib/es6/Stdlib_Map.js index ae1b9f17e6..27d7851b1a 100644 --- a/lib/es6/Stdlib_Map.js +++ b/lib/es6/Stdlib_Map.js @@ -1 +1,11 @@ -/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ + + + +function isEmpty(map) { + return map.size === 0; +} + +export { + isEmpty, +} +/* No side effect */ diff --git a/lib/es6/Stdlib_Set.js b/lib/es6/Stdlib_Set.js index ae1b9f17e6..469aca57da 100644 --- a/lib/es6/Stdlib_Set.js +++ b/lib/es6/Stdlib_Set.js @@ -1 +1,11 @@ -/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ + + + +function isEmpty(set) { + return set.size === 0; +} + +export { + isEmpty, +} +/* No side effect */ diff --git a/lib/js/Stdlib_Array.js b/lib/js/Stdlib_Array.js index f5313a3e3b..e62a6efb4e 100644 --- a/lib/js/Stdlib_Array.js +++ b/lib/js/Stdlib_Array.js @@ -22,6 +22,10 @@ function fromInitializer(length, f) { return arr; } +function isEmpty(arr) { + return arr.length === 0; +} + function equal(a, b, eq) { let len = a.length; if (len === b.length) { @@ -182,6 +186,7 @@ exports.make = make; exports.fromInitializer = fromInitializer; exports.equal = equal; exports.compare = compare; +exports.isEmpty = isEmpty; exports.indexOfOpt = indexOfOpt; exports.lastIndexOfOpt = lastIndexOfOpt; exports.reduce = reduce; diff --git a/lib/js/Stdlib_Dict.js b/lib/js/Stdlib_Dict.js index 107ab353c9..668d9354ba 100644 --- a/lib/js/Stdlib_Dict.js +++ b/lib/js/Stdlib_Dict.js @@ -9,6 +9,10 @@ function size(dict) { return Object.keys(dict).length; } +function isEmpty(dict) { + return Object.keys(dict).length === 0; +} + function forEach(dict, f) { Object.values(dict).forEach(value => f(value)); } @@ -28,6 +32,7 @@ function mapValues(dict, f) { exports.$$delete = $$delete$1; exports.size = size; +exports.isEmpty = isEmpty; exports.forEach = forEach; exports.forEachWithKey = forEachWithKey; exports.mapValues = mapValues; diff --git a/lib/js/Stdlib_Map.js b/lib/js/Stdlib_Map.js index ae1b9f17e6..a3844084bd 100644 --- a/lib/js/Stdlib_Map.js +++ b/lib/js/Stdlib_Map.js @@ -1 +1,9 @@ -/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ +'use strict'; + + +function isEmpty(map) { + return map.size === 0; +} + +exports.isEmpty = isEmpty; +/* No side effect */ diff --git a/lib/js/Stdlib_Set.js b/lib/js/Stdlib_Set.js index ae1b9f17e6..d74649202f 100644 --- a/lib/js/Stdlib_Set.js +++ b/lib/js/Stdlib_Set.js @@ -1 +1,9 @@ -/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ +'use strict'; + + +function isEmpty(set) { + return set.size === 0; +} + +exports.isEmpty = isEmpty; +/* No side effect */ diff --git a/runtime/Stdlib_Array.res b/runtime/Stdlib_Array.res index 534c631c7c..6591ae8928 100644 --- a/runtime/Stdlib_Array.res +++ b/runtime/Stdlib_Array.res @@ -44,6 +44,8 @@ let fromInitializer = (~length, f) => @get external length: array<'a> => int = "length" +let isEmpty = arr => arr->length === 0 + let rec equalFromIndex = (a, b, i, eq, len) => if i === len { true diff --git a/runtime/Stdlib_Array.resi b/runtime/Stdlib_Array.resi index 617060db10..99dc5aecfd 100644 --- a/runtime/Stdlib_Array.resi +++ b/runtime/Stdlib_Array.resi @@ -80,6 +80,24 @@ someArray->Array.length == 2 @get external length: array<'a> => int = "length" +/** +`isEmpty(array)` returns `true` if the array is empty (has length 0), `false` otherwise. + +## Examples + +```rescript +[]->Array.isEmpty->assertEqual(true) +[1, 2, 3]->Array.isEmpty->assertEqual(false) + +let emptyArray = [] +emptyArray->Array.isEmpty->assertEqual(true) + +let nonEmptyArray = ["hello"] +nonEmptyArray->Array.isEmpty->assertEqual(false) +``` +*/ +let isEmpty: array<'a> => bool + // TODO: Docs @deprecated("Use `copyWithin` instead") @send external copyAllWithin: (array<'a>, ~target: int) => array<'a> = "copyWithin" @@ -909,7 +927,7 @@ external mapWithIndex: (array<'a>, ('a, int) => 'b) => array<'b> = "map" /** `reduce(xs, init, fn)` -Applies `fn` to each element of `xs` from beginning to end. Function `fn` has two parameters: the item from the list and an “accumulator”; which starts with a value of `init`. `reduce` returns the final value of the accumulator. +Applies `fn` to each element of `xs` from beginning to end. Function `fn` has two parameters: the item from the list and an "accumulator"; which starts with a value of `init`. `reduce` returns the final value of the accumulator. ## Examples @@ -928,7 +946,7 @@ let reduce: (array<'a>, 'b, ('b, 'a) => 'b) => 'b /** `reduceWithIndex(x, init, fn)` -Applies `fn` to each element of `xs` from beginning to end. Function `fn` has three parameters: the item from the array and an “accumulator”, which starts with a value of `init` and the index of each element. `reduceWithIndex` returns the final value of the accumulator. +Applies `fn` to each element of `xs` from beginning to end. Function `fn` has three parameters: the item from the array and an "accumulator", which starts with a value of `init` and the index of each element. `reduceWithIndex` returns the final value of the accumulator. ## Examples diff --git a/runtime/Stdlib_Dict.res b/runtime/Stdlib_Dict.res index 5adb4f0667..21c2bbe331 100644 --- a/runtime/Stdlib_Dict.res +++ b/runtime/Stdlib_Dict.res @@ -20,6 +20,8 @@ let delete = (dict, string) => { let size = dict => dict->keysToArray->Stdlib_Array.length +let isEmpty = dict => dict->size === 0 + @val external valuesToArray: dict<'a> => array<'a> = "Object.values" @val external assign: (dict<'a>, dict<'a>) => dict<'a> = "Object.assign" diff --git a/runtime/Stdlib_Dict.resi b/runtime/Stdlib_Dict.resi index f3e9a24abb..bd8edc864a 100644 --- a/runtime/Stdlib_Dict.resi +++ b/runtime/Stdlib_Dict.resi @@ -163,6 +163,25 @@ dict->Dict.size->assertEqual(1) */ let size: dict<'a> => int +/** +`isEmpty(dictionary)` returns `true` if the dictionary is empty (has no key/value pairs), `false` otherwise. + +## Examples +```rescript +let emptyDict = Dict.make() +emptyDict->Dict.isEmpty->assertEqual(true) + +let dict = Dict.make() +dict->Dict.set("someKey", 1) +dict->Dict.isEmpty->assertEqual(false) + +// After clearing all keys +dict->Dict.delete("someKey") +dict->Dict.isEmpty->assertEqual(true) +``` +*/ +let isEmpty: dict<'a> => bool + /** `valuesToArray(dictionary)` returns an array of all the values of the dictionary. diff --git a/runtime/Stdlib_Map.res b/runtime/Stdlib_Map.res index 7d534aa7d8..2c821c0021 100644 --- a/runtime/Stdlib_Map.res +++ b/runtime/Stdlib_Map.res @@ -7,6 +7,8 @@ type t<'k, 'v> @get external size: t<'k, 'v> => int = "size" +let isEmpty = map => map->size === 0 + @send external clear: t<'k, 'v> => unit = "clear" @send external forEach: (t<'k, 'v>, 'v => unit) => unit = "forEach" diff --git a/runtime/Stdlib_Map.resi b/runtime/Stdlib_Map.resi index cc4767e90e..8068ab9109 100644 --- a/runtime/Stdlib_Map.resi +++ b/runtime/Stdlib_Map.resi @@ -95,6 +95,25 @@ let size = map->Map.size // 1 @get external size: t<'k, 'v> => int = "size" +/** +`isEmpty(map)` returns `true` if the map has no key/value pairs, `false` otherwise. + +## Examples +```rescript +let emptyMap = Map.make() +emptyMap->Map.isEmpty->assertEqual(true) + +let map = Map.make() +map->Map.set("someKey", "someValue") +map->Map.isEmpty->assertEqual(false) + +// After clearing the map +map->Map.clear +map->Map.isEmpty->assertEqual(true) +``` +*/ +let isEmpty: t<'k, 'v> => bool + /** Clears all entries in the map. diff --git a/runtime/Stdlib_Set.res b/runtime/Stdlib_Set.res index 61040dc863..02a21ad1c9 100644 --- a/runtime/Stdlib_Set.res +++ b/runtime/Stdlib_Set.res @@ -7,6 +7,8 @@ type t<'a> @get external size: t<'a> => int = "size" +let isEmpty = set => set->size === 0 + @send external clear: t<'a> => unit = "clear" @send external add: (t<'a>, 'a) => unit = "add" diff --git a/runtime/Stdlib_Set.resi b/runtime/Stdlib_Set.resi index 36312a84aa..5f9ca18668 100644 --- a/runtime/Stdlib_Set.resi +++ b/runtime/Stdlib_Set.resi @@ -94,6 +94,25 @@ let size = set->Set.size // 2 @get external size: t<'a> => int = "size" +/** +`isEmpty(set)` returns `true` if the set has no values, `false` otherwise. + +## Examples +```rescript +let emptySet = Set.make() +emptySet->Set.isEmpty->assertEqual(true) + +let set = Set.make() +set->Set.add("someValue") +set->Set.isEmpty->assertEqual(false) + +// After clearing the set +set->Set.clear +set->Set.isEmpty->assertEqual(true) +``` +*/ +let isEmpty: t<'a> => bool + /** Clears all entries in the set. From 2ac4490eb8fef060704ead4f77f748272193a671 Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Sat, 24 May 2025 18:14:37 +0400 Subject: [PATCH 5/8] Make Stdlib.Dict more performant --- lib/es6/Stdlib_Dict.js | 29 +++++++++++++++------------ lib/js/Stdlib_Dict.js | 29 +++++++++++++++------------ runtime/Stdlib_Dict.res | 44 ++++++++++++++++++++++++++--------------- 3 files changed, 60 insertions(+), 42 deletions(-) diff --git a/lib/es6/Stdlib_Dict.js b/lib/es6/Stdlib_Dict.js index 6741659742..eacc31af55 100644 --- a/lib/es6/Stdlib_Dict.js +++ b/lib/es6/Stdlib_Dict.js @@ -13,22 +13,25 @@ function isEmpty(dict) { return Object.keys(dict).length === 0; } -function forEach(dict, f) { - Object.values(dict).forEach(value => f(value)); -} +let forEach = ((dict, f) => { + for (var key in dict) { + f(dict[key]); + } + }); -function forEachWithKey(dict, f) { - Object.keys(dict).forEach(key => f(dict[key], key)); -} +let forEachWithKey = ((dict, f) => { + for (var key in dict) { + f(dict[key], key); + } + }); -function mapValues(dict, f) { - let target = {}; - Object.keys(dict).forEach(key => { - let value = dict[key]; - target[key] = f(value); +let mapValues = ((dict, f) => { + var target = {}; + for (var key in dict) { + target[key] = f(dict[key]); + } + return target; }); - return target; -} export { $$delete$1 as $$delete, diff --git a/lib/js/Stdlib_Dict.js b/lib/js/Stdlib_Dict.js index 668d9354ba..b943cac08c 100644 --- a/lib/js/Stdlib_Dict.js +++ b/lib/js/Stdlib_Dict.js @@ -13,22 +13,25 @@ function isEmpty(dict) { return Object.keys(dict).length === 0; } -function forEach(dict, f) { - Object.values(dict).forEach(value => f(value)); -} +let forEach = ((dict, f) => { + for (var key in dict) { + f(dict[key]); + } + }); -function forEachWithKey(dict, f) { - Object.keys(dict).forEach(key => f(dict[key], key)); -} +let forEachWithKey = ((dict, f) => { + for (var key in dict) { + f(dict[key], key); + } + }); -function mapValues(dict, f) { - let target = {}; - Object.keys(dict).forEach(key => { - let value = dict[key]; - target[key] = f(value); +let mapValues = ((dict, f) => { + var target = {}; + for (var key in dict) { + target[key] = f(dict[key]); + } + return target; }); - return target; -} exports.$$delete = $$delete$1; exports.size = size; diff --git a/runtime/Stdlib_Dict.res b/runtime/Stdlib_Dict.res index 21c2bbe331..b61d80b2ed 100644 --- a/runtime/Stdlib_Dict.res +++ b/runtime/Stdlib_Dict.res @@ -28,22 +28,34 @@ let isEmpty = dict => dict->size === 0 @val external copy: (@as(json`{}`) _, dict<'a>) => dict<'a> = "Object.assign" -let forEach = (dict, f) => { - dict->valuesToArray->Stdlib_Array.forEach(value => f(value)) -} - -@inline -let forEachWithKey = (dict, f) => { - dict->keysToArray->Stdlib_Array.forEach(key => f(dict->getUnsafe(key), key)) -} - -let mapValues = (dict, f) => { - let target = make() - dict->forEachWithKey((value, key) => { - target->set(key, f(value)) - }) - target -} +// Use %raw to support for..in which is a ~5% faster than .forEach +let forEach: (dict<'a>, 'a => unit) => unit = %raw(` + (dict, f) => { + for (var key in dict) { + f(dict[key]); + } + } +`) + +// Use %raw to support for..in which is a ~5% faster than .forEach +let forEachWithKey: (dict<'a>, ('a, string) => unit) => unit = %raw(` + (dict, f) => { + for (var key in dict) { + f(dict[key], key); + } + } +`) + +// Use %raw to support for..in which is a ~5% faster than .forEach +let mapValues: (dict<'a>, 'a => 'b) => dict<'b> = %raw(` + (dict, f) => { + var target = {}; + for (var key in dict) { + target[key] = f(dict[key]); + } + return target; + } +`) external has: (dict<'a>, string) => bool = "%dict_has" From 8123cc83b4879b01e6c4ff953507ed543ff22649 Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Sun, 3 Aug 2025 23:59:55 +0400 Subject: [PATCH 6/8] Use more optimised implimintation for Dict.size & Dict.isEmpty --- lib/es6/Stdlib_Dict.js | 51 ++++++++++++++++++-------------- lib/js/Stdlib_Dict.js | 51 ++++++++++++++++++-------------- runtime/Stdlib_Dict.res | 65 +++++++++++++++++++++++------------------ 3 files changed, 94 insertions(+), 73 deletions(-) diff --git a/lib/es6/Stdlib_Dict.js b/lib/es6/Stdlib_Dict.js index eacc31af55..c57b1e1fb0 100644 --- a/lib/es6/Stdlib_Dict.js +++ b/lib/es6/Stdlib_Dict.js @@ -5,33 +5,40 @@ function $$delete$1(dict, string) { delete(dict[string]); } -function size(dict) { - return Object.keys(dict).length; -} - -function isEmpty(dict) { - return Object.keys(dict).length === 0; -} - let forEach = ((dict, f) => { - for (var key in dict) { - f(dict[key]); - } - }); + for (var i in dict) { + f(dict[i]); + } +}); let forEachWithKey = ((dict, f) => { - for (var key in dict) { - f(dict[key], key); - } - }); + for (var i in dict) { + f(dict[i], i); + } +}); let mapValues = ((dict, f) => { - var target = {}; - for (var key in dict) { - target[key] = f(dict[key]); - } - return target; - }); + var target = {}, i; + for (i in dict) { + target[i] = f(dict[i]); + } + return target; +}); + +let size = ((dict) => { + var size = 0, i; + for (i in dict) { + size++; + } + return size; +}); + +let isEmpty = ((dict) => { + for (var _ in dict) { + return false + } + return true +}); export { $$delete$1 as $$delete, diff --git a/lib/js/Stdlib_Dict.js b/lib/js/Stdlib_Dict.js index b943cac08c..b38f7a9c40 100644 --- a/lib/js/Stdlib_Dict.js +++ b/lib/js/Stdlib_Dict.js @@ -5,33 +5,40 @@ function $$delete$1(dict, string) { delete(dict[string]); } -function size(dict) { - return Object.keys(dict).length; -} - -function isEmpty(dict) { - return Object.keys(dict).length === 0; -} - let forEach = ((dict, f) => { - for (var key in dict) { - f(dict[key]); - } - }); + for (var i in dict) { + f(dict[i]); + } +}); let forEachWithKey = ((dict, f) => { - for (var key in dict) { - f(dict[key], key); - } - }); + for (var i in dict) { + f(dict[i], i); + } +}); let mapValues = ((dict, f) => { - var target = {}; - for (var key in dict) { - target[key] = f(dict[key]); - } - return target; - }); + var target = {}, i; + for (i in dict) { + target[i] = f(dict[i]); + } + return target; +}); + +let size = ((dict) => { + var size = 0, i; + for (i in dict) { + size++; + } + return size; +}); + +let isEmpty = ((dict) => { + for (var _ in dict) { + return false + } + return true +}); exports.$$delete = $$delete$1; exports.size = size; diff --git a/runtime/Stdlib_Dict.res b/runtime/Stdlib_Dict.res index b61d80b2ed..7a146b30ec 100644 --- a/runtime/Stdlib_Dict.res +++ b/runtime/Stdlib_Dict.res @@ -18,44 +18,51 @@ let delete = (dict, string) => { @val external keysToArray: dict<'a> => array = "Object.keys" -let size = dict => dict->keysToArray->Stdlib_Array.length - -let isEmpty = dict => dict->size === 0 - @val external valuesToArray: dict<'a> => array<'a> = "Object.values" @val external assign: (dict<'a>, dict<'a>) => dict<'a> = "Object.assign" @val external copy: (@as(json`{}`) _, dict<'a>) => dict<'a> = "Object.assign" -// Use %raw to support for..in which is a ~5% faster than .forEach -let forEach: (dict<'a>, 'a => unit) => unit = %raw(` - (dict, f) => { - for (var key in dict) { - f(dict[key]); - } +// Use %raw to support for..in which is a ~10% faster than .forEach +let forEach: (dict<'a>, 'a => unit) => unit = %raw(`(dict, f) => { + for (var i in dict) { + f(dict[i]); } -`) - -// Use %raw to support for..in which is a ~5% faster than .forEach -let forEachWithKey: (dict<'a>, ('a, string) => unit) => unit = %raw(` - (dict, f) => { - for (var key in dict) { - f(dict[key], key); - } +}`) + +// Use %raw to support for..in which is a ~10% faster than .forEach +let forEachWithKey: (dict<'a>, ('a, string) => unit) => unit = %raw(`(dict, f) => { + for (var i in dict) { + f(dict[i], i); } -`) - -// Use %raw to support for..in which is a ~5% faster than .forEach -let mapValues: (dict<'a>, 'a => 'b) => dict<'b> = %raw(` - (dict, f) => { - var target = {}; - for (var key in dict) { - target[key] = f(dict[key]); - } - return target; +}`) + +// Use %raw to support for..in which is a ~10% faster than .forEach +let mapValues: (dict<'a>, 'a => 'b) => dict<'b> = %raw(`(dict, f) => { + var target = {}, i; + for (i in dict) { + target[i] = f(dict[i]); + } + return target; +}`) + +// Use %raw to support for..in which is a ~10% faster than Object.keys +let size: dict<'a> => int = %raw(`(dict) => { + var size = 0, i; + for (i in dict) { + size++; + } + return size; +}`) + +// Use %raw to support for..in which is a 2x faster than Object.keys +let isEmpty: dict<'a> => bool = %raw(`(dict) => { + for (var _ in dict) { + return false } -`) + return true +}`) external has: (dict<'a>, string) => bool = "%dict_has" From 8835c5503c25638df2d7c48a8636b88ceaf0fa66 Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Mon, 4 Aug 2025 00:01:20 +0400 Subject: [PATCH 7/8] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2917714028..7fc8d8366d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ #### :rocket: New Feature +- Add new Stdlib helpers: `String.capitalize`, `String.isEmpty`, `Dict.size`, `Dict.isEmpty`, `Array.isEmpty`, `Map.isEmpty`, `Set.isEmpty`. https://github.com/rescript-lang/rescript/pull/7516 + #### :bug: Bug fix - Fix formatting of nested records in `.resi` files. https://github.com/rescript-lang/rescript/pull/7741 From 669de383ffbfdf7e2c9482eae3f9af080776f83c Mon Sep 17 00:00:00 2001 From: Dmitry Zakharov Date: Mon, 4 Aug 2025 11:47:14 +0400 Subject: [PATCH 8/8] Fix tests --- .../tests-reanalyze/deadcode/package.json | 2 +- tests/analysis_tests/tests/package.json | 2 +- .../tests/src/expected/Completion.res.txt | 10 ++++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/package.json b/tests/analysis_tests/tests-reanalyze/deadcode/package.json index a73dcaf9bc..c9ff3996f7 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/package.json +++ b/tests/analysis_tests/tests-reanalyze/deadcode/package.json @@ -3,7 +3,7 @@ "private": true, "scripts": { "build": "rescript legacy build", - "clean": "rescript clean" + "clean": "rescript legacy clean" }, "dependencies": { "@rescript/react": "link:../../../dependencies/rescript-react", diff --git a/tests/analysis_tests/tests/package.json b/tests/analysis_tests/tests/package.json index 601884c23c..01bbe0ecbb 100644 --- a/tests/analysis_tests/tests/package.json +++ b/tests/analysis_tests/tests/package.json @@ -3,7 +3,7 @@ "private": true, "scripts": { "build": "rescript legacy build", - "clean": "rescript clean" + "clean": "rescript legacy clean" }, "dependencies": { "@rescript/react": "link:../../dependencies/rescript-react", diff --git a/tests/analysis_tests/tests/src/expected/Completion.res.txt b/tests/analysis_tests/tests/src/expected/Completion.res.txt index 3361871351..802aebe53b 100644 --- a/tests/analysis_tests/tests/src/expected/Completion.res.txt +++ b/tests/analysis_tests/tests/src/expected/Completion.res.txt @@ -405,7 +405,7 @@ Path Array. "kind": 12, "tags": [], "detail": "(array<'a>, 'b, ('b, 'a) => 'b) => 'b", - "documentation": {"kind": "markdown", "value": "\n`reduce(xs, init, fn)`\n\nApplies `fn` to each element of `xs` from beginning to end. Function `fn` has two parameters: the item from the list and an “accumulator”; which starts with a value of `init`. `reduce` returns the final value of the accumulator.\n\n## Examples\n\n```rescript\nArray.reduce([2, 3, 4], 1, (a, b) => a + b) == 10\n\nArray.reduce([\"a\", \"b\", \"c\", \"d\"], \"\", (a, b) => a ++ b) == \"abcd\"\n\n[1, 2, 3]->Array.reduce(list{}, List.add) == list{3, 2, 1}\n\nArray.reduce([], list{}, List.add) == list{}\n```\n"} + "documentation": {"kind": "markdown", "value": "\n`reduce(xs, init, fn)`\n\nApplies `fn` to each element of `xs` from beginning to end. Function `fn` has two parameters: the item from the list and an \"accumulator\"; which starts with a value of `init`. `reduce` returns the final value of the accumulator.\n\n## Examples\n\n```rescript\nArray.reduce([2, 3, 4], 1, (a, b) => a + b) == 10\n\nArray.reduce([\"a\", \"b\", \"c\", \"d\"], \"\", (a, b) => a ++ b) == \"abcd\"\n\n[1, 2, 3]->Array.reduce(list{}, List.add) == list{3, 2, 1}\n\nArray.reduce([], list{}, List.add) == list{}\n```\n"} }, { "label": "sliceToEnd", "kind": 12, @@ -430,6 +430,12 @@ Path Array. "tags": [], "detail": "(array<'a>, int, 'a) => unit", "documentation": {"kind": "markdown", "value": "\n`set(array, index, item)` sets the provided `item` at `index` of `array`.\n\nBeware this will *mutate* the array.\n\n## Examples\n\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\narray->Array.set(1, \"Hello\")\n\narray[1] == Some(\"Hello\")\n```\n"} + }, { + "label": "isEmpty", + "kind": 12, + "tags": [], + "detail": "array<'a> => bool", + "documentation": {"kind": "markdown", "value": "\n`isEmpty(array)` returns `true` if the array is empty (has length 0), `false` otherwise.\n\n## Examples\n\n```rescript\n[]->Array.isEmpty->assertEqual(true)\n[1, 2, 3]->Array.isEmpty->assertEqual(false)\n\nlet emptyArray = []\nemptyArray->Array.isEmpty->assertEqual(true)\n\nlet nonEmptyArray = [\"hello\"]\nnonEmptyArray->Array.isEmpty->assertEqual(false)\n```\n"} }, { "label": "filterWithIndex", "kind": 12, @@ -507,7 +513,7 @@ Path Array. "kind": 12, "tags": [], "detail": "(array<'a>, 'b, ('b, 'a, int) => 'b) => 'b", - "documentation": {"kind": "markdown", "value": "\n`reduceWithIndex(x, init, fn)`\n\nApplies `fn` to each element of `xs` from beginning to end. Function `fn` has three parameters: the item from the array and an “accumulator”, which starts with a value of `init` and the index of each element. `reduceWithIndex` returns the final value of the accumulator.\n\n## Examples\n\n```rescript\nArray.reduceWithIndex([1, 2, 3, 4], 0, (acc, x, i) => acc + x + i) == 16\n\nArray.reduceWithIndex([1, 2, 3], list{}, (acc, v, i) => list{v + i, ...acc}) == list{5, 3, 1}\n\nArray.reduceWithIndex([], list{}, (acc, v, i) => list{v + i, ...acc}) == list{}\n```\n"} + "documentation": {"kind": "markdown", "value": "\n`reduceWithIndex(x, init, fn)`\n\nApplies `fn` to each element of `xs` from beginning to end. Function `fn` has three parameters: the item from the array and an \"accumulator\", which starts with a value of `init` and the index of each element. `reduceWithIndex` returns the final value of the accumulator.\n\n## Examples\n\n```rescript\nArray.reduceWithIndex([1, 2, 3, 4], 0, (acc, x, i) => acc + x + i) == 16\n\nArray.reduceWithIndex([1, 2, 3], list{}, (acc, v, i) => list{v + i, ...acc}) == list{5, 3, 1}\n\nArray.reduceWithIndex([], list{}, (acc, v, i) => list{v + i, ...acc}) == list{}\n```\n"} }, { "label": "some", "kind": 12,