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.
Yes. Removing it would be even better. I thought you might wanna namespace it to provide backwards compatibility for some people who still might be depending on it?
If you keep the auto-open of Stdlib I think it will be more confusing and more error-inducing for 99% of the people than leaving it out (or keeping it under a namespace).
It does not matter to the editor plugin.
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
module String = Js.String2
module JsArray = Js.Array2
Because I find Js.Blah2.bleh is very unreadable in the code. I also have to use JsArray because I use both Belt.Array functions as well as push and others from Js.Array2.
Very confusing overall
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.
OCaml, and also Javascript, generally falls in the latter philosophy.
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
About what stdlibs should be accessible: I like the idea of being able to configure in bsconfig.json
which standard libraries shall be available. We could choose among:
-
js
: new unified/cleaned upJs
module -
js-legacy
: oldJs
andJs2
modules belt
ocaml
To match the current behavior, the default would be:
"stdlib": [ "js-legacy, belt, ocaml" ]
As a library author, like @ryyppy suggested, I might like to make sure that only Js
is accessible, so I would set:
"stdlib": [ "js" ]
As an application author, I would like to make sure that I am not inadvertently using something from the OCaml stdlib, so I might set
"stdlib": [ "js-legacy, belt" ]
for an existing project or
"stdlib": [ "js, belt" ]
for a new project or even, once all functionality available in Js
is also present in Belt
"stdlib": [ "belt" ]
As for what should be opened by default: It is already possible today to globally open Belt
by specifying
"bsc-flags": [ "-open Belt" ],
in bsconfig.json
.
Maybe, if only one stdlib
was specified, it could also be opened automatically.
I am not a big fan of using open
. Is that lots of typing compared with below?
module L = Belt.List
let {map, filter , every} = module (Belt.List)
If Belt is affordable, that is the only lib you need, it is actually more performant than JS, just a bit more intrusive than JS, so Js may fit a niche use case when you want to convert existing Js code into ReScript
I used to use belt as it is (not opening via compiler flag) but i ened up creating lots of aliases and opens at start of expressions / modules.
This is what I was talking about in my point. Most of Js is runtime free, but there are also parts of Belt that are optimized and necessary for writing clean JS, like Belt.Array.getUnsafe
mentioned in another post.
Js and Belt are very linked, to the point where it is confusing to beginners to the language (where I count myself in). I’m sure if you are used to the distinction and the names it feels like it is not a problem, but it is a big unnecessary detail.
For example, like knowing that for working with arrays here is the array
type globally available, there is Js.Array2
, and there is Js.Vector
(??? just learnt about this), and there is Belt.Array
, all with different functions, some that are just like the Js functions, some that are mutable utilities, some that treat the array as immutable. This is a lot of incidental complexity that is fully self-created, and if you are cleaning up, it is the time to address it.
Think about who you are targeting, is it converting JS users? If that is an important audience, think about what they do. For example with Array:
- There is one array type
- Solution: Unify all the array modules to make it easy in the autocomplete and docs
- I call functions on it without even knowing the module type
- Solution: Well it is not possible to do this, but reducing the api surface to one name
xs->Array.length
is the shortest we can do.
Anything longer,Js.Array2.
,Belt.Array
,Js.Vector
all lose in length to `` which is the prefix JS developers have to use to call an array function.
- Solution: Well it is not possible to do this, but reducing the api surface to one name
- To know if an Array function is mutable or immutable, as a Js dev I rely on the docs mainly
- Solution: Great, we have great docs. We can also split the functions in the docs by mutable / immutable, and on each function clearly specify if the array is mutated in place. We also have the return types which are a good indicator of the mutability. Highlight this.
Anyway just an example, and my perspective. You can always get used to the idiosincrasies of each language but if you are cleaning up with breaking changes, it may be worth rethinking the design in a more holistic way. Maybe it is just my Elm experience showing here, the standard library is a pleasure to learn, very well designed and documented.
Yes that would be a great idea!
I don’t know if it’s worth the trouble. Adding a library dependency is trivial in ReScript. You might just go ahead and remove the Stdlib
library altogether once you are confident you cover everything. Anyone who wants it back can install it separately.
Maintain the legacy stdlib is non-trivial work, there are lots of C shims to support in the compiler, it can not be maintained by 3rd party.
As we discussed, the retirement plan for ocaml stdlib would be that we first have a flag to allow users to explicitly opt-out; later opt-out by default and final removal
Reading all that, I think that a -stdlib-future
flag would be great.
I’m currently experimenting a bit on what would make a newcomer experience easier by only exposing a subset of Pervasives, and I came down to the following module: rescript-js/ReScriptJs__Pervasives.res at main · bloodyowl/rescript-js · GitHub
I also think that, apart from the internals like Obj.camlEqual
, CamlInternalLazy
, internal int32 and so on, this flag could only make it so that only the following modules would be exposed:
Lazy
-
Int64
(whileBigInt
still has some light support, but that could be implemented user-land)
What do you think?
Can you go to the pervasives module and mark functions not needed as deprecated, or we put it in a separate module, e.g Belt.Float for lots of float operations in Pervasives?
-stdlib-future is to ensure that none of the legacy stdlib will be exposed,
we can provide more polished APIs in Belt.Lazy/Belt.Int64 etc.
Contributions are welcome
I’m in favour of this, although the OCaml standard library doesn’t impact my project too much. Some context may be useful for the discussion.
The project I’m working on has a stdlib replacement combining elements of Belt
and Js
along with some custom sprinkles and renaming methods to match existing libraries in our main TypeScript codebase. It may even be open source by the end of the year.
The only files that don’t have open Tiny
at the top are the ones the stdlib depends on. If/when we’re able to switch to pinned packages in our monorepo we’ll consider something like "bsc-flags": [ "-open Tiny" ]
or similar in the individual package bsconfig.json
files.
Relevant GH issue: Add BigInt primitive number type · Issue #4677 · rescript-lang/rescript-compiler · GitHub
When is getting rid of default ocaml library planned? I was already confused by it when I used some arrays.
Thanks. Can it be deactivated already with some compiler flag?