Hello all!
I’m worried a little by the recent RFC discussions:
They got traction, and I think it’s a question of time when they would be introduced to the language. I understand I cannot stop the train (aka progress), and I don’t want to stop it. I just want to share my mind.
From my point of view, these two features make the language less orthogonal. This might lead to pointless community splits inside and across various projects written in ReScript. Should we use semicolons or not, how thingsShouldBeNamed, should we prefer data-first or data-last… These questions have a default of enforced answers. And that’s great!
The two active proposals will pose new questions. How should I design a thing constructor?
// This
module Delivery: {
type t
let make: (
~code: string,
~price: Money.t,
~tariffCode: string=?,
~city: string,
~cityCode: string=?,
~address: string,
~pickupPointCode: string=?,
~comment: string,
unit,
) => t
}
// or this?
module Delivery: {
type t
@obj
type opts = {
code: string,
price: Money.t,
tariffCode: option<string>,
city: string,
cityCode: option<string>,
address: string,
pickupPointCode: option<string>,
comment: string,
}
let make: (opts) => t
}
I suspect many will prefer the latter because it’s a pattern from the JS land. And it’s OK, it’s not a problem at all. The problem are the questions: “why the first way exists at all?”, “what the hell this trailing unit and () I see here and there?”. So, to become proficient in ReScript I’ll have to learn more. Of course, we cannot simply drop the labeled argument feature for the reason of backward compatibility. And we cannot refuse to introduce the structural typing of records because in other scenarios (interop) it helps a lot.
But can’t we stop for a moment and think how the two ways could co-exist and be mostly interchangeable? Those who know Python can remember the **kwargs
idiom: it acts as a dictionary and an argument list simultaneously! Yeah, I know, technically, that’s another story, but I’m talking about the beauty of such dualism.
The same goes for “private by default”. I already have a powerful and working mechanism for this: the unfamous *.resi
files. And I’m happy with it.
Although I see no problem with making *.resi
for almost everything, I can understand the people who are enchanted by the pub
/ export
concept. They either feel that *.resi
acts as a boilerplate brake lowering productivity, or they are disappointed with IDE experience, which cannot guess what file it should navigate. I think I’m in the minority with my style of development (interface and docs first, then peer review, then implementation), and that’s OK. The problem is the questions: “should I use pub/export or module signatures?”, “if I may use both, why should I expose everything twice?”, “if export is enough, why *.resi
ever exist?”.
I think the module signatures give the language a HUGE feature: the clear, uncluttered and dense API declarations. I suspect we cannot drop module signatures anyway because of the backward compatibility. And we cannot stop the progress from introducing the pub
/ export
because they are so desirable by the community.
So, again, can we think about the elegant co-existence of two paradigms to make them interchangeable? It’s hard to provide a concrete example here, but is there a way to satisfy *.resi
defenders and export
defenders at the same time?
The *.resi
defenders value the clear API definition (they don’t value having two files for every module, I guess). The export
defenders value single-file encapsulation and fast DX. So, can we move all *.resi
inside *.res
and reuse the existing mechanisms to make both sides happy?
Thank you.