type a = {
t ?: string,
l ?: int,
p ?: array<int>
}
type b = {
t ?: string,
l ?: string,
p ?: string
}
let intToString : option<int> => option<string> = (s) => {
switch s{
| Some(r) => Some("int")
| None => None
}
}
let arrayToString : option<array<int>> => option<string> = (s) => {
switch s{
| Some(r) => Some("array")
| None => None
}
}
let mapper: (a) => b = (a) => {
({ t : ?a.t
, l : ?a.l->intToString
, p : ?a.p->arrayToString
} : b)
}
let s = mapper({t : "Testing" })
Above line is giving this output { t : "Testing", l : undefined, p : undefined }
but I wanted to get this output { t : "Testing" }
I was thinking of writing this code in Js to get my use case.
function mapper(a) {
const result = {};
if (typeof a.t !== "undefined") result.t = a.t;
if (typeof a.l !== "undefined") result.l = intToString(a.l);
if (typeof a.p !== "undefined") result.p = arrayToString(a.p);
return result;
}
So, I tried writing this in Rescript, But failed because of compilation errors .
Also, if rescript’s com gives a feature where we asign optional to a optional field and code gets generated like this. Then, we can get type safety with this feature
Ex :
let mapper: (a) => b = (a) => {
({ t ?: a.t
, l ?: a.l->intToString
, p ?: a.p->arrayToString
} : b)
}
I can hypothesize that the problem could be related to some kind of TS type or some strict runtime check. In other words it could be something like the following:
interface B {
t?: string;
l?: string;
p?: string;
}
Or, related to runtime check, t in a gives different results than a.t !== undefined for those cases.
Yes, because if null is a one million dollar mistake, JS had the nice idea to have 3 different ways of expressing a slightly different variant of a null value (undefined, null and the absence of the field).
I would like to get an answer from the OP as well, but at the same time I am curious about possible approaches in Rescript (other than going %raw). Said that, there are a lot of JS projects out there that don’t even distinguish a null from a 0 or an empty string, so…
EDIT: just to throw ideas, this should work, but obviously it requires a bit of boilerplate.
Partially yes. Sorry, I have not been specific with my words – the boilerplate I am referring to is the need of a bMutable type to being able to modify the record. But anyway, I think that this problem only exists because of some terrible design decisions in JS, I am open to ideas but I am not sure Rescript should actually have some additional features to handle this case in a more ergonomic way.
I’m creating a JSX using the generic JSX feature of ReScript. We have a native cross-platform renderer that requires a prop in string format. However, I don’t want developers to write margins in string format like “20,20,20,20”. To address this, I’ve defined two prop types:
type margin = Margin(int, int, int, int)
type props = {key?: string, margin?: margin}
type renderProps = {key?: string, margin?: string}
Now, I need a mapper function to convert between these types. However, I encountered an issue where all keys were converting to undefined. My native renderer checks for the presence of props using hasOwnProperty and other contracts. I don’t want to change my native renderer, and I also dislike the idea of writing a JavaScript loop to remove all keys with undefined values.
I’m unable to think of any solution from the ReScript side either…
It’s not pretty, but the boilerplate can be written in rescript, via the object spread operator. Working off of the initial example:
type a = {
t ?: string,
l ?: int,
p ?: array<int>
}
type b = {
t ?: string,
l ?: string,
p ?: string
}
let updateT : (b, a) => b = (b, a) => {
switch a.t {
| Some(r) => {...b, t: r}
| None => b
}
}
let updateL : (b, a) => b = (b, a) => {
switch a.l {
| Some(r) => {...b, l: "int"}
| None => b
}
}
let updateP : (b, a) => b = (b, a) => {
switch a.p {
| Some(r) => {...b, p: "array"}
| None => b
}
}
let mapper: (a) => b = (a) =>
{}
->updateT(a)
->updateL(a)
->updateP(a)
let mapper: (a) => b = (a) => {
({ t ?: a.t
, l ?: a.l->intToString
, p ?: a.p->arrayToString
} : b)
}
But in this also, If you remove any key for code … rescript will compile successfully. There is no way to ensure all keys are copied. That needs to be manage by team.