I need to have a Dict with int keys and int values (want to count number appearances).
I see there is Js.Dict that does not have a method to see if specific key is in dict.
I also see that Belt.Set.Dict and Belt.Map.Dict (both appear in the docs as SetDict and MapDict which is again confusing) do have get but they need a comparator function.
(1) Is there a reason there is no generic Dict in Belt?
Eventually I thought using Js.Dict where the keys should be string.
I wanted to start with an empty Js.Dict:
let dict = Js.Dict.empty
and check/add values:
let keyOption = Js.Dict.get(dict, "1")
let newDict = Js.Dict.set(dict, "1", 0)
and for both line I got an error:
This call is missing an argument of type unit
After some time I saw the signature of the Js.Dict.empty is
let empty: unit => t<'a>
seems like it returns a function (2) why? The the docs say (and I would expect it to be so):
“Returns an empty dictionary.”
Adding () to the get/set function seems to get rid of the error, but the code is inside Array.reduce() and next time the dict will not be empty…
I would never look for Map while I need a Dict. Maybe because I saw Dict in another languages, but also because I see Dict in the docs.
Here I encountered into duplication of implementations and terms again - many Dict-s and Map-s which also behave like Dict-s.
My question is - why Js.Dict.empty returns something else that is not actually Js.Dict.t?
And as a suggestion for docs:
it would be so much easier to find things if the entries would be alphabetically sorted.
Seeng MapInt in docs I would search for Belt.MapInt which does not exist. Seems like there should be another level of nesting or at least written as Map.Int.
To expand on yaramin’s answer:
The Js.Dict api is mutable. Calling Js.Dict.set on a dict will mutate the underlying js object.
Therefore if Js.Dict.empty were a value (just an empty js object), every occurrence would reference exactly the same object.
To circumvent this empty is a function returning a new empty Dict.
@ryyppy I second that. - At least in the api docs this would make a lot of sense to me.
The difference is in the underlying implementation and their performance characteristics:
Js.Dict provides an api to work with js objects like you would do in js. The only restriction in rescript is, you can’t use polymorhpic objects with this api.
Belt.Map is a very performant generic implementation of a map structure. This introduces some minimal runtime “overhead” traded for different performance characteristics
Belt.MapInt is a provided default paramerization of Belt.Map due to it’s frequency of usage. - the same is true for Belt.MapString
While the structures and types are very similar they have different dis-/advantages and performance characteristics.
Js.Dict is mutable and for Js.Dict.set there is a note at the end of the function description.
I need to add () for functions expecting unit, like for Js.Dict.empty(). This is a little confusing given the fact that in many other cases, especially when piping functions, the parenthesis are not needed: number->Belt.Int.toString / number->Js.Int.toString.
We should probably put in the Js.Dict module description that this is a mutable representation of a JS object…
That is an inconsistency we sadly have to live with right now.
Rule of thumb: You always need to provide parenthesis for a function call, but you may need () for pipe calls, depending on the arguments.
It makes sense when you think about how -> is being interpreted:
"test"->Js.String2.toUpperCase
Is essentially transformed into
Js.String2.toUpperCase("test")
If you’d do "string"->Js.String2.toUpperCase() it would turn into Js.String2.toUpperCase("test", ()) which is wrong from an arity perspective.
We can’t special case toUpperCase(), because ReScript is a curried language with possible optional labeled arguments.
For example, whenever your last argument would be an optional label, you’d need to provide a final () to tell the compiler that we are applying the function.