We had some discussions around the roles for such libraries, below are some guidelines:
Belt would be the most comprehensive library, if you can afford Belt, Belt is the only library you should rely on. There are some missing features in Belt, we plan to add it later, contributions are welcome.
JS is planned for migration from Js to ReScript, so it is mostly external bindings, it is in theory less intrusive than Belt when introducing ReScript into your existing codebase, contributions to more JS APIs are welcome.
Pervasives/OCaml stdlib. This is the library we inherited from OCaml, it will be kept as is. We may put it under a separate namespace called Caml.
almost anyone developing apps wants to have one standard library. We, for one, ended up with a bunch of modules that include Belt modules and add some functions (some functions use JS modules, because we don’t want to use JS in the app code directly)
JS might make sense for lib authors (because apps usually can afford Belt)
Caml: should ReScript even expose its OCaml legacy? Or is it just for backwards compatibility?
agreed, OCaml stdlib could probably be either removed or at least placed behind a namespace, it’s the least optimized of all 3 but the most accessible right now.
If the OCaml stdlib is removed or moved to another namespace, shouldn’t Belt be opened by default?
Yeah these are the crucial details we still need to figure out.
If Belt would be opened by default, it would mean that Belt, as a term, would completely disappear, since we are now referring to the modules as List, Array, etc. This might be confusing. It might also be cumbersome for cases where users want to actually open Js by default to get access to JS idiomatic bindings like Array and Dict. I’d prefer the latter for many different scenarios → use zero-cost by default, but use Belt explicitly as a proper stdlib if needed.
Also, as @hoichi correctly stated: Library maintainers would probably want to use zero-cost APIs over Belt.
^ Note: We want to take the chance to finally fix the confusing Js.Array, Js.Array2 situation as well (e.g. getting rid of the pipe-last versions of the zero-cost bindings / remove Js.List / adding the new Promise bindings). This will require careful coordination, since we want to allow (automated) migration to the new core apis (or at least don’t break existing users during the transition phase).
The most promising idea was to iterate on the new Js implementation outside of the compiler (pretty much what @bloodyowl did with rescript-js). More concrete plans and details will follow soon (hopefully).
Ideally it shouldn’t be exposed by default so it doesn’t randomly show up in auto-completion. I could either imagine having a configuration flag (for “ocaml legacy mode”), or just make Caml.ml available as a curated library… no matter what, our users will be able to use the original APIs for backwards compat.
I think one of the more confusing set of modules is Array / Belt.Array / Js.Array(2). The Belt and Js versions offer a lot of redundant functionality, so it isn’t often clear which one “should” be used.
Making it more complicated is the array[index] syntax, which desugars to “whichever Array.get happens to be in scope.” It creates confusing situations like when the OCaml Array.get raises exceptions or when open Belt turns all of your values from arrays into options and creates lots of type errors you need to fix.
I just want to share my perspective since it seems to differ from other commenters.
Typing e.g. arr->Js.Array2.map is pretty verbose when compared with JS’s arr.map. Replacing “Js” with “ReScript” seems like it would exacerbate this. Preferrably, I just type ->Array.map.
My ideal would be to have Js.* open by default. This seems to align with ReScript’s philosophy as it has minimal runtime and API surface area.
I think users who want to avoid Belt entirely should be able to do so (ideally, being able to exclude it from the project entirely so that others on the team cannot use it). It should not get any first-class treatment, but instead be treated as a 3rd-party library, like lodash.
I think arr[X] should return option() by default, and users should have to use some special operator/function to do an unsafe get.
He is probably not aware of this because it’s neither documented, nor did I, nor anyone else on the team ever use that feature yet. I am not even sure if we highlight that particular syntax consistently in all our different editor plugins right now.
Our editor support autocompletes to ->Js.Array2 etc, so it would certainly be nice if it could just expand to ->Array instead.
Good to know, thanks. Although this trades off ease of refactoring (if I decide I want to remove filter, or add a reduce, I need to go update the import) for the extra concision, so I personally would prefer using the fully qualified names.
I think that’s a good plan for what to do with the Ocaml stlib. The tricky thing is to decide whether to make Belt or Js the default. Could this be configurable too as there seems to be a definite split in preference.
My 2 cents, because I haven’t seen this perspective mentioned:
There should only be one standard library, and it should be open by default. The name should hidden and transparent to users of the language.
Anything else is extremely confusing for learning the language, how the documentation navigates and reads, and the readability of the code.
So many mainstream languages have a standard library that is readily available and not namespaced or opt in. From my following the language from the sidelines, it seems like Rescript has inherited the very confusing OCaml situation of having so many standard libraries and options. As a non-OCaml user, I’ll remind you, this is very confusing specially to beginners in the language.
If there is a need to clarify which functions are low-cost Js interop, or treat the data structure as mutable / immutable (Js.Array2.* vs Belt.Array.*), this should be done in the documentation of the functions for editor use, and in the website by having sections / separations between them and the code docs rendered.
I’ve personally been using “-open Belt” in my bsc-flags, and I’ve also had to many times do aliases like
The key question is: do you include batteries in your stdlib or not?
One philosophy is to provide a standard library which is quite large and provides a default set of tooling for the problems of the day. A non-academic data point: “does the standard library include a JSON encoder/decoder?”
Another philosophy is to have the standard library provide the bare minimum, namely things that can’t be implemented in the language directly. Your language might not have specific notation for arrays say, and you bounce that into a library. Yet, it is hard to implement arrays without some help from the underlying runtime for efficiency reasons. So that gets included in the stdlib.
It is a trade-off as well, with strengths and weaknesses. For instance, if most of the stdlib lives independently of the compiler, you can avoid synchronization of releases, which is a nice thing. On the other hand, you risk having multiple implementations of the same thing, and different libraries end up cleaving and isolating the community in multiple groups.
I’m more familiar with Janes Street Core libraries:
Base - Absolute minimum. High portability. Extremely high stability.
Core_kernel - Extends Base. Pulls in some dependencies, more features, High stability, but the API morphs more.
Core - Extendends Core_kernel. Adds UNIX APIs.
This layered approach is nice because packages can “graduate” into the kernel once they are good enough: Belt.String, Belt.Date, Belt.Promise might be examples.
I have some better ideas to make OCaml stdlib not accessible with a flag without introducing a separate namespace.
So the plan is to have a flag called -stdlib-future, that will only make Belt and Js accessible. It will be off by default for some time to make time for people’s transition, then on by default, after some time, we then remove the legacy stdlib, let me know what you think