tablecloth
tablecloth copied to clipboard
Change of direction
After a few years experience with this library, and given the changes in the ecosystem over that time, I think it would be wise to change the purpose of Tablecloth somewhat.
Problem
I think the original goal - to be able to reuse code between bucklescript and OCaml - is not as good a goal as I initially thought.
My experience with trying to share code between the platforms has not been great - we only share some very basic types and very little logic. We tried to share some less basic types, but we needed to use ppxes that were only available on one or another, and used different annotations in each.
Moreover, the goal of sharing types on server and client is less useful than originally imagined. Client and server move at different paces, with for example the server able to change fairly instantly while clients take longer, while also the server has serialization concerns that cause types to sometimes persist much longer than on the client.
To add to this, ReScript and OCaml are diverging more and more. ReScript is very much focussing on its mission of supporting JS, and over time has focused much less on any connection to OCaml, including - significantly - sticking with an older version of the OCaml compiler. The focus on t-first
also means that trying to overlay a t-agnostic
library doesn't hold up as well.
ReScript also has a formidable standard library in Belt - I notice that Tablecloth's Map does not have a compatible type with the Rescript Map.
At the same time, OCaml is moving forward at a good pace too. It has moved forward many versions, as has Base/Core. I haven't perceived any changes that would affect us, but I'm sure they will come.
Solution
As I look to how we actually use Tablecloth at Dark, the most valuable thing has not been reusing code (as stated above, this has been quite a bad experience really), but rather having the same APIs available on all platforms. It is annoying when using multiple MLs to have to look up what the function is called in F# vs OCaml vs ReScript.
So I'd like to switch Tablecloth to a somewhat purer form of its original goal: to be an overlay that provides the same API on many ML platforms: OCaml, ReScript, and F# (and in the future other MLs too).
This would mean:
- the primary function of the library is to have functions of the same name on each platform (for example, all platforms would have a
Map.values
orString.join
function), with the signature being close but also closely tied to the platform (t-first in ReScript and t-last in F#, for instance) - code reuse is not a goal, just API similarity
- functions should only be named in the style of what is idiomatic on that platform (camelCase on Rescript and F#, snake_case in OCaml) - this is a problem as having two names in each cloggs up the autocomplete which annoys people
- Set and Map types would be aliases for the idiomatic Set/Map used on each platform, not our own type
Comments?
I'd love some comments on this proposal. It is a move away from how we designed tablecloth, but I think it is more appropriate given how the ecosystem has been moving.
@Dean177 particularly interested in your thoughts
Sounds amazing, is there any roadmap with intermediate steps of this transition? Would love to contribute
My rough idea of a roadmap:
- release current version
- add F# version from https://github.com/darklang/dark/tree/main/fsharp-backend/src/Tablecloth
- release again
- now start changing direction:
- remove rescript and f# snake_case versions
- remove ocaml camlcase functions
- make f# functions t-last
- make ocaml function t-last
- add ocaml-related ppx things as a separate library
- make rescript functions t-first
- remove custom tablecloth maps
- remove custom tablecloth sets
Ok, I'll try to help you with some of the points:
✅ Remove rescript snake_case versions (PR #233) ✅ Remove ocaml camlcase functions ⬜ Make ocaml function t-last ⬜ Make rescript functions t-first
@pbiggar starting the work on migrating rescript functions to be data-first, just want to clarify your vision on the design.
From my understanding, moving to data-first will eliminate labeled ~f
argument, but other labeled arguments will be preserved, since they provide clarity, and not data agnostisity.
Example:
[| 1; 2; 3; 4; 5; 6 |] |. filter ~f:Int.isEven
becomes [| 1; 2; 3; 4; 5; 6 |] |. filter Int.isEven
map2 ~f:( + ) [| 1; 2; 3 |] [| 4; 5; 6 |]
becomes map2 [| 1; 2; 3 |] [| 4; 5; 6 |] ( + )
but
sliding [| 1; 2; 3; 4; 5 |] ~size:2 ~step:3
and
42 |. repeat ~length:2
will stay the same
Such functions will still be data-agnostic, but this will be a side effect, not a feature.
Did I understood your vision correctly?
That's a good question. In OCaml, t-last is desirable, but also labelled arguments are the norm, so it makes sense in OCaml to keep the labels where they are.
In F#, there are no labels, so we just need to make sure everything is t-last.
In ReScript, I'm not so sure. ReScript supports labelled arguments for sure: is it normal to use labelled arguments? That said, for ~f
, the f
argument is not the t
in t-first
(that would be the list, map, etc). It could be that the only thing to change are things like map union and list concatenation that take two arguments of the same type (though I'm speaking off the cuff there).
In ReScript, I'm not so sure. ReScript supports labelled arguments for sure: is it normal to use labelled arguments? That said, for ~f, the f argument is not the t in t-first (that would be the list, map, etc). It could be that the only thing to change are things like map union and list concatenation that take two arguments of the same type (though I'm speaking off the cuff there).
Having thought more about this, we should remove labels for rescript, as they're not idiomatic.