Parenthesis in a type definition like your external don’t really matter to the compiler because functions are curried by default.
Meaning both are equal:
external a: x => y => z = "fn"
external b: (x => y) => z = "fn"
In your case, the easiest solution is to use your useStore type inside of your external create definition.
Oh interesting. Does that mean type signatures don’t follow the substitution principle (or perhaps just for types on external functions)? That would be very surprising to me. I get that => in function signatures is right associative.
To my knowledge this behavior (differentiation of an extra type vs inline types) is only in external definitions.
Otherwise (non-externals) a function of
let f: (string => string) => (string => string)
would behave the same way if it were defined like
type returnFn = string => string
let f: (string => string) => returnFn
What would be the best way to represent an (external) JS function with 1 mandatory param and one optional boolean param, e.g. (a, b = false) => { ... } ? I’ve tried:
Two ordered params (a, b: bool) => ...: this works but that means that you have to pass the 2nd param every time
One ordered param and one optional keyword param (~b: bool=?, a): this doesn’t work as it seems that in actual application, when the default value is not overridden (i.e. the fn is passed one param) it passes the ordered param second (which I suppose makes sense?) and passes undefined as the first param.
One ordered param, one optional keyword param, and unit: (a, ~b: bool=?, unit): this doesn’t really have any advantage over the first idea as it would be called with a and ()…and it doesn’t work–a call like fn(foo, ()) attempts to pass undefined to the return value of the externally defined fn
Your right: example
I guess if the js api can’t handle undefined as arg, I’d probably use two external definitions.
If you don’t mind an extra function call you could then wrap the two externals in one function call with your desired api.
Unfortunately this is not part of the top-level API of zustand (the package i’m interacting with)–it’s the signature of a function that it passes in as a param (it’s the set function in my earlier example) so I’m not sure if you can use external for it.
The issue is that in the Curry._3 call: we have Curry._3(set, newState, undefined, undefined). Since set has an arity of 2, Curry._3 attempts to call the return value of set(newState, undefined) with undefined, but set returns undefined, so it errors.
It seems like it’s not an issue for Zustand if you call it’s set function with set({someObj}, undefined, undefined): Codesandbox
Therefore I think the issue is with the curry call.
You could take inspiration from the react bindings and always type to the most secure version of a function: Meaning just type set as (.store, bool) => unit
I adapted your example to prevent any currying here.
I looked at Zustand's api: The issue with Zustand is, that it’s highly polymorphic. It’s hard to create general purpose bindings for such js apis in general. (if your goal is to still have accurate types)
You would either have a lot of unsafe types or specific types for your concrete use-case. - like your types in your example
Do you have a specific use-case in your code-base for this library or are you trying to come up with general purpose bindings?
What is the reason, you’re prefering to use Zustand?
I’m just exploring Rescript. I was interested in the appeal of a lang that sat somewhere between Elm & Typescript on the continuum of permissiveness vs. safety. Zustand was a more of a test of “how difficult would it be to use a very dynamic external library written in JS with Rescript?” For a green-field project though, you’re right, I would probably use useReducer or reductive.