{..} means any object so I assumed you want that.
You don’t have to supply 'a in that type,
in your example you can use myFn type like this:
@send external doSomething: (int, {..}) => unit = "doSomething"
let myFn = (a, b) => doSomething(a, b)
type myFn<'a> = (int, {..} as 'a) => unit
let createSomething: unit => myFn<_> = () => {
myFn
}
but I think you don’t want “any object” which is ofcourse a bit unsafe.
can you write an example in ts or other language so I can better understand what you actually want to do.
I agree that any object is unsafe, but it is the use case I have - the keys are dynamic based on API responses and the values can be either a string or a function
The TypeScript version (which is more type-safe than the ReScript version I’m trying to achieve) is:
type Input = Record<string, string | number | ((x: string) => string)>
// @send external doSomething: (int, {..}) => unit = "doSomething"
const doSomething = (a: number, arg: Input): void => undefined
// type myFn<'a> = (int, {..} as 'a) => unit
type MyFn = (a: number, arg: Input) => void
// let myFn = (a, b) => doSomething(a, b)
const myFn: MyFn = (a, b) => doSomething(a, b)
/*
let createSomething: unit => myFn<_> = () => {
myFn
}
*/
const createSomething: () => MyFn = () => myFn
I understand that in OCaml and ReScript you can have real arguments polymorphism and you can use discriminated unions / separate functions. But at the same time {..} seems to be a workaround that when it comes to interfacing with externals. My question is why {..} works as expected when defining an external:
@send external doSomething: (int, {..}) => unit = "doSomething"
and then it is inferred correctly when calling that external
depending on runtime information I have to call it with:
doSomething(1, { "arg1": 1, "arg2: x => x.toUpperCase() })
or
doSomething(1, { "arg3": "1" })
and etc. So I’m looking for a zero-runtime cost to provide this Input arg - {..} is not perfect, but it did just fine
The issue with Js.Dict.t is that you have to create it “indirectly”, for example with fromArray or fromList - you can’t build it with 0 cost. There’s also additional runtime cost for converting from discriminated union - I would need to have something like:
module MyModule = {
type t
type arg = String(string) | Int(int) | Fn(string => string)
@val external doSomething: (int, Js.Dict.t<t>) => unit = "doSomething"
external unsafeOfString: string => t = "%identity"
external unsafeOfInt: int => t = "%identity"
external unsafeOfFn: (string => string) => t = "%identity"
let doSomething = (a, b: array<(string, arg)>) => {
let b = Js.Array2.map(b, ((k, v)) => {
let v = switch v {
| String(x) => unsafeOfString(x)
| Int(x) => unsafeOfInt(x)
| Fn(x) => unsafeOfFn(x)
}
(k, v)
})
doSomething(a, Js.Dict.fromArray(b))
}
}
that’s a lot of generated runtime code for something that should be 0 cost (and is with the TypeScript snippet above), so I’m hoping to find something even suboptimal, but 0 cost
thanks amiralies, there is definetely less code generated:
old doSomething, mutation doSomething2, but I’m hoping to make doSomething3 work (which is less type safe, but is 0 cost):