Do you want to use
Math? Or anything that you don't have to first
Then, make sure it's not already on https://github.com/reasonml-community or NPM.
Now, here's how you bind to a JS value:
external setTimeout : (unit -> unit) -> int -> float = "setTimeout" [@@bs.val] external clearTimeout : float -> unit = "clearTimeout" [@@bs.val]
[@bs.val] external setTimeout : (unit => unit, int) => float = "setTimeout"; [@bs.val] external clearTimeout : float => unit = "clearTimeout";
setTimeout methods and the corresponding
external's type annotation specifies that
- takes a function that accepts
unit(which on the JS side turns into a function that accepts nothing and returns nothing. More on modeling functions later)
- and an integer that specifies the duration before calling said function
- returns a number that is the timeout's ID. This number might be big, so we're modeling it as a float rather than the 32-bit int
Tips & Tricks
When the name you're using on the BS side matches the JS value you're modeling, you can use the empty string shorthand:
external clearTimeout : float -> unit = "" [@@bs.val]
[@bs.val] external clearTimeout : float => unit = "";
The above still isn't ideal. See how
setTimeout returns a
clearTimeout accepts one. There's no guarantee that you're passing the float created by
clearTimeout! For all we know, someone might pass it
Math.random() into the latter.
We're in a language with a great type system now! Let's leverage a popular feature to solve this problem: abstract types.
type timerId external setTimeout : (unit -> unit) -> int -> timerId = "setTimeout" [@@bs.val] external clearTimeout : timerId -> unit = "clearTimeout" [@@bs.val]
type timerId; [@bs.val] external setTimeout : (unit => unit, int) => timerId = "setTimeout"; [@bs.val] external clearTimeout : timerId => unit = "clearTimeout";
timerId is a type that can only be created by
setTimeout! Now we've guaranteed that
clearTimeout will be passed a valid ID. Whether it's a number under the hood is now a mere implementation detail.
Inspect the output here. It's as clean as hand-written JS code, except you know it comes from correctly typed BuckleScript. If these kind of features are all you use from BuckleScript, you'd already have derived values from them!
This trick is often used to allow folks to agree on what JS functionalities to bind to, without prescribing how to bind to them. For example, the BS library exposes a few abstract types like
window. The DOM API is huge and extremely hard to bind to correctly; we don't provide an opinionated way of doing it, so exposing the types and allowing you to use them as input/output types of your
externals and have everyone's opinionated wrapper still agreeing on the types is a great middle-ground. You, for example, might only need 3 methods from DOM, and so wrote your own, thin wrappers for them.
If you want to bind to a value inside a global module, e.g.
Math.random, attach a
bs.scope to your
external random: unit -> float = "random" [@@bs.val][@@bs.scope "Math"] let someNumber = random ()
[@bs.scope "Math"] [@bs.val] external random : unit => float = "random"; let someNumber = random();
you can bind to an arbitrarily deep object by passing a tuple to
external length: int = "length" [@@bs.val][@@bs.scope "window", "location", "ancestorOrigins"]
[@bs.val] [@bs.scope ("window", "location", "ancestorOrigins")] external length : int = "length";
This binds to