Advice on standard library to use


#1

For our newly ported 10k bucklescript app, what standard library should my team use?

Context

[I’ve read Collections: OCaml standard library vs. Belt vs. Immutable-re but have different concerns so making a new thread.]

We just did a port of our Elm application to bucklescript, using the OCaml syntax. We also write our backend in OCaml, heavily using the Jane Street ecosystem (Base/Core, dune, lots of ppxes, etc). We’re using bucklescript-tea for our framework, and use almost no JS libraries outside our core app, though we hope to change that.

When porting, I ended up writing a shim around all the Elm libraries, and occasionally porting some of them, but now that we’re done we need to think about how to remove this debt, and decide on our team’s best practices going forward.

Options

Use Belt

While porting, I tried using Belt directly, and found it very challenging.

We use |> a lot, while Belt seems designed for |. There seems to have been a huge argument about t-in-front vs t-in-back, and the end result was t-in-front which works poorly for us: we’d like to share code with our backend which uses |> everywhere, and our frontend is currently written using |> everywhere as well.

[On the server, we avoid these problems by using Jane Street Core, which uses labels almost everywhere, so the “does-the-t-go-first-or-last” is irrelevant in practice. Surprisingly, Belt doesn’t use labelled arguments very much, which would obviate the problem. We also use the OCaml syntax, so the reason syntax alternatives aren’t available.]

Belt also feels very inconsistent. I would repeatedly have to look up functions as I never got an intuitive sense of which arguments go where. It also has its functions spread across the Belt and Js namespaces: for example there is no Belt.String so we have to use Js.String, and I recall their being functions in Js.Map that weren’t in Belt.Map (can I use them? probably not)

It also feels very incomplete compared to the Jane Street and Elm ecosystems we’re used it, so I found myself writing a bunch of functions myself or porting the Elm functions directly.

Finally, having read lots of github issues, it seems that contributing to Belt involves a lot of work: there appears to be a lot of context involved, and long and contentious discussions, so I’m not confident that we will be able to upstream the work we do to mitigate our problems.

I’d love to hear whether people agree with our (early, limited) assessment, and what strategies people have used to mitigate the issues I’ve seen so far. I’d also be interested in challenges I haven’t come across yet.

Use Jane Street Base/Core

It would be great if we’d be able to use the same libraries on our frontend and backend, esp if it led to code reuse. Does anyone use Base in Bucklescript - I don’t see how to do it? What are the pitfalls? How does one even start with testing this out?

Port Elm libraries

We got spoiled by the very rich Elm standard library, and well as the *-Extra community libraries. One option is to port them over to bucklescript. Our app’s port was largely automated using an elm->ocaml compiler (to be open sourced soon), so this would be pretty cheap for us. We however don’t know the performance and other implications of this - what risks do people see?

Not using Belt

Given there are Js and of course the built-in ocaml libraries, one option would be to use them and skip Belt. However, they also seem quite incomplete and inconsistent to each other, and so would have many of the same problems that we’ve seen with Belt.


Tablecloth - a new standard library for OCaml and ReasonML
#2

Similar concerns.

We have the same Reason code running both on server and browser. In the server it runs on Node, but we want to move to native OCaml (with Jane Street’s Base) in the future.

This is a weird place to be in - our codebase started before Belt, and we used stdlib collection modules (Set, Map etc.) extensively. As per Belt’s documentation, they don’t map nicely to Javascript since they use functors which creates large closures. We however occasionally use convenience functions from Belt (eg: Belt.List.hasAssoc) when they are unavailable in stdlib. We try to keep this to a minimum so we can port to native in the future. I also for reasons of familiarity like |> over ->.

There seem to be three choices:

  • Fully adopt Belt - is there a native version in the works?
  • Move to js_of_ocaml and use Base on both browser and client. (we have code that relies on ReasonReact which might have to be rewritten)
  • Create a Belt wrapper and do conditional compilation for server and client.

#3

bsb native may help you.


#4

Have you looked at the OCaml-containers wrapper for BuckleScript? https://github.com/cxa/bs-containers-core


#5

Hi @Rogs. This looks incredibly interesting but it is, shall we say, rather sparse on documentation. Do you have experience using it in production or even better can you provide some examples?


#6

The Containers library does look comprehensive: https://c-cube.github.io/ocaml-containers/last/containers/index.html

Curious to hear about experience from people using it in production and how it compares to Base and Belt (esp. functors vs phantom types).


#7

@pbiggar Have you taken a look at bucklescript-tea ? It’s a bucklescript library that mirrors the Elm 0.18 Api mostly, but there are a few differences. Unfortunately, there’s no documentation yet, you just have to take a look at the code. (Not too hard) It’s mostly naming things, like in the Html library, you use class’ vs class, input’ vs input, etc … Actually now that I think about it, its uses ' a lot. Also, the starting program function is named differently.

You can take a look at these

  • Bucklescript Tea Chess tutorial
  • Bucklescript-tea-game-overbots tutorial

for examples of how to use it. (They are kind of complicated for me though)

I really like the library though since I can still use the TEA (The Elm Architecture) and their API mostly.

I also just decided to retheme the standard library docs using bucklescript-tea and you can take a look at the code here if you like: https://github.com/seadynamic8/ocaml-modern-docs
or the actual website if you need a prettier looking docs: https://www.streamingspring.com/ocaml/docs/
Just keep in mind that its the latest version of OCaml (4.07) and not the one Bucklescript uses (4.02.3) and I don’t have any of the submodules listed there yet. (Work in progress)

I am also planning on writing a guide for people going from Elm to Bucklescript-tea (hopefully) :slight_smile:


#8

Yes, we’ve been using Bucklescript-tea. Seems fast and complete, though very lacking in docs. We’re going to start upstreaming some stuff to it soon.


#9

In general Belt is more efficient than Base or Core and produce less JS code so that it can be put in production in some strict environment, it is indeed less feature rich.

I am a bit confused about t-first/t-last argument since Base also adopts t-first convention

Finally, having read lots of github issues, it seems that contributing to Belt involves a lot of work: there appears to be a lot of context involved, and long and contentious discussions, so I’m not confident that we will be able to upstream the work we do to mitigate our problems.

I hope this can be improved, but would be also interested in some concrete feedback


#10

We are going to upgrade to OCaml 4.06.2 soon


#11

@bobzhang Would it be possible to use Belt in native OCaml? I’m presuming phantom types will work well in both Javascript and native runtimes.


#12

I didn’t actually know Core was t-first because it is also t-last: almost every function only has 1 positional argument, and the other arguments use labels.

Consider https://ocaml.janestreet.com/ocaml-core/latest/doc/base/Base/String/index.html. Here are some signatures chosen arbitrarily from the middle:

val lsplit2 : t ‑> on:char ‑> (t * t) option
val rsplit2 : t ‑> on:char ‑> (t * t) option
val split : t ‑> on:char ‑> t list
val split_on_chars : t ‑> on:char list ‑> t list
val split_lines : t ‑> t list
val lfindi : ?⁠pos:int ‑> t ‑> f:(int ‑> char ‑> bool) ‑> int option
val rfindi : ?⁠pos:int ‑> t ‑> f:(int ‑> char ‑> bool) ‑> int option
val lstrip : ?⁠drop:(char ‑> bool) ‑> t ‑> t
val rstrip : ?⁠drop:(char ‑> bool) ‑> t ‑> t

Every one of those can has only positional parameter, and so can be trivially curried, and piped to using both |> and |..


#13

This. I did a few problems on Excercism with Base and never once thought it to be t-first (I did all the piping |>). It seems like Base is designed to be t-ambivalent. Given that Belt uses labeled params anyway, maybe it makes sense for it to adopt that approach too.

Bear in mind that I’m a total noob in FP though.


#14

See this blog: https://blog.janestreet.com/core-principles-uniformity-of-interface/, t first was mentioned explicitly. We follow the same convention for various reasons, it actually will generate more efficient code in some cases in the future (due to that type info flow direction)

Labels are introduced when you have several arguments with the same type, otherwise it brings too much noise in the type signature.

For the universal code gen, |. is also more efficient than |>


#15

I was not saying that Core was t-last. I was saying that labels solve the problem. I hadn’t realized you were against labels. What is it that makes them noisy? That is not my experience at all. I find them extremely clear, and they allow currying with any argument, which is extremely powerful and elegant, and which I end up using a lot.

That said, this thread isn’t a discussion of Belt’s relative merits. Belt has it’s own priorities and they do not seem to be the same as mine. I believe that if Belt supported labels there would be more reasons to use it, currently my feelings using it are similar to when I used PHP - it feels inconsistent and frustrating. However, there are larger issues that I’m sure you’re considering.


#16

I tend to agree with @pbiggar on labels. Jane Street Base uses labels extensively. It labels both ~init and ~f for all its folds, even though the types are obviously different. It labels ~f for its maps too, even though it’s the only argument besides t.

Labels make your code more readable at a glance. And Jane Street write the library for themselves, first and foremost, and they are in the position to hire the best of the best. Yet they prefer to be explicit and reduce the mental load of their brilliant devs. Reason, on its part, is supposed to be widely adopted, by developers of wildly varying skills level that also have to learn styles, browser quirks, and whatnot, so I think readability is way more important here. Terseness, less so.

BTW, I guess the article you link to is pretty old because Yaron writes:

Sadly, this often conflicts with the most useful order for partial application.

And labeling everything solves that problem too. I’ve yet to run into problems with partially applied functions from JSB, whether I pipe them or pass them to higher order functions.

Also, only label arguments of the same type is a pretty straightforward rule, but still not as straightforward as label everything but t. So the latter would probably make for a more uniform interface.

But that’s not as important as the fact you can see what parameters mean at a glance.


#17

Back to the original question, has anyone had success using something like Base with bucklescript? It seems like it might be possible to do with bsb-native, but only with native builds.

There’s a really old issue thread on the official Bucklescript repo about whether it’s possible to easily include native libs, and so far it hasn’t presented a better way than just manually building it with bsc and then publishing on npm.


#18

Has anyone had any luck with https://github.com/darklang/tablecloth?

It works in both the native and bucklescript ecosystems, wrapping janestreet/Base in native and wrapping Belt in bucklescript.


#19

I wrote that lib in response to the lack of solid answers in this thread. We’re using it on our frontend, but haven’t converted the backend yet. So far so good, but it’s very early, there’s still a lot of functions missing, and some with questionable names or definitions. I’d love people to try it and open issues with feedback!


#20

Looks sweet! A few questions regarding web and Belt.

  1. How big is the overhead, performance-wise and size-wise? Do the bindings result in a lot of extra JS? I mean, one of Belt’s perks is how small and close-to-js it is.
  2. How do you plan to deal with missing functionality in Belt? Include it in Tablecloth? Contribute to Belt?
  3. How do you plan to deal with changes in Belt? Belt seems pretty undermanned and raw at the moment, so is probably subject to change.
  4. If one uses Tablecloth-over-Belt, would you rather recommend contributing to Belt or to Tablecloth?