I’m trying to convert a generative functor to the new syntax. Here’s the Reason code:
module type Primitive = {
type t;
let compare: (t, t) => int;
}
module Fresh = (P: Primitive, ()) :
({
type t;
type internal;
let inject: internal => t;
let project: t => internal;
let compare: (t, t) => int;
let equal: (t, t) => bool;
} with type internal = P.t) => {
type t = P.t;
type internal = t;
external inject: internal => t = "%identity";
external project: t => internal = "%identity";
let compare = P.compare;
let equal = (a, b) => P.compare(a, b) == 0;
}
But when I try to convert it to the new syntax, ReScript parser seems to have issue with the unit argument in this line:
module Fresh = (P: Primitive, ()) :
Can it be done in ReScript at all? Will it be supported in the future? Are there workarounds to make the numerous Fresh(String).t types incompatible (other than using Reason/OCaml syntax for the functors)?
Potentially, maybe it would be even nicer if ReScript supported units of measure, like F#. Then again, units of measure don’t suppose parsing (I think), and there are a lot of cases when a valid primitive value is not a valid entity value.
Yes, there is a workaround: to annotate the Fresh(...) output modules at the point of binding them. This forces the compiler to throw away its knowledge that the t types are the same. E.g.:
module type Primitive = Set.OrderedType;
module type FRESH = {
include Primitive;
type internal;
let inject: internal => t;
let project: t => internal;
let equal: (t, t) => bool;
};
module Fresh(P: Primitive) = {
type t = P.t;
type internal = t;
let inject = internal => internal;
let project = t => t;
let compare = P.compare;
let equal = (a, b) => compare(a, b) == 0;
}
module M1: FRESH with type internal = string = Fresh(String);
module M2: FRESH with type internal = string = Fresh(String);
let m1 = M1.inject("");
let m2 = M2.inject("");
let _ = M1.compare(m1, m2);
This gives:
We've found a bug for you!
OCaml preview 24:23-24
This has type:
M2.t
But somewhere wanted:
M1.t
The use case is to have zero-cost distinct types (so you cannot use name instead of email, account number instead of transaction sum, etc. As I said, I’m probably OK if ReScript has first-class support for those cases one day, but today, as far as I know, OCaml cannot give you this functionality without applicative functors at all.
So… Fresh(String) has the same module type as Fresh(String), but types of FRESH with type internal = string are distinct?
Thanks a lot for the workaround, I’m just pondering how the type system works.
If we don’t annotate the module types, the compiler infers a ‘full’ type of the module, including the relation that t = string on both the modules. But if we annotate the module type, then it’s restricted to only the information that’s available from the module type FRESH with type internal = string. In this module type, there is no relation type t = string, only type t. And the compiler doesn’t equate type t from different modules as the same type.
I dont know if you heard about redux-saga. The reason I am looking at rescript is because typescript does not work well with redux-saga. It will color whole files in red, because it cant deal with sagas the way it should. Sagas are generators that get advanced ( gen.next() calls) by the redux-saga redux middleware. It sounds weird but is actually a super awesome library and solves my problem. ( I work on a browserwallet atm)
The only downside is that I am stuck with javascript without any typesystem because of this issue.
I think if rescript could handle this usecase very well, this would be a huge selling point. browser wallets and these kinds of more complex browser plugins in general have huge potential for growth.
I would also offer to create some propaganda about this, if we manage to find a beautiful solution to this problem.
Thanks, and hello everyone (this is my first post here lol )
Kind Regards,
Spirobel