Skip to content

Instantly share code, notes, and snippets.

@cristianoc
Last active April 26, 2023 13:31
Show Gist options
  • Save cristianoc/00e760e1d5605ddc36fba29d1b1f14c3 to your computer and use it in GitHub Desktop.
Save cristianoc/00e760e1d5605ddc36fba29d1b1f14c3 to your computer and use it in GitHub Desktop.

Proposal for Handling Bindings in ReScript

1. Overview

The purpose of this proposal is to outline a design for handling bindings in ReScript that is safe and easy to use. Bindings are an important aspect of ReScript, as they allow developers to use external JavaScript code and libraries in their ReScript projects.

Currently, ReScript provides several ways to handle bindings, including using the @module annotation and the %raw syntax. While these methods work well, they can be complex and error-prone, especially when dealing with large and complex external libraries.

In this document, we will explore two alternatives for handling bindings in ReScript without using import. The first approach combines the @module and %raw annotations, while the second approach utilizes a single @module annotation for more concise and straightforward code.

2. Design

2.1 Combining @module and %raw

To handle bindings in ReScript without using import, one approach is to use a combination of the @module and %raw annotations. The @module annotation allows you to import an external module and bind it to a value, while the %raw annotation lets you embed raw JavaScript code in your ReScript file.

Here's an example of how you could use both @module and %raw to achieve a similar result:

    type path = {
      dirname: string => string,
    }

    // Using @module to import 'path' and binding it to a value
    @module("path") external path: path = "default"

    // Binding to the external JavaScript API using %raw
    let getName: window => string = %raw(`x => x.name`)

This code imports the path module using the @module annotation and binds it to the path value. Then, it uses %raw to bind to the name property of the window object in JavaScript.

2.2 Using a single @module annotation

A more concise and straightforward approach is to use a single @module annotation to import an external module and bind it to a value.

Here's an example using the Garmin API with a single @module annotation:

    // Define a type for the Garmin API
    type garminApi = {
      getHeartRateData: () => array<int>,
      getElevationData: () => array<int>,
    }

    // Import the Garmin API using @module and bind it to a value
    @module("garmin-api") external garminApi: garminApi = "default"

    // Access the getHeartRateData and getElevationData functions from the Garmin API
    let heartRateData = garminApi.getHeartRateData()
    let elevationData = garminApi.getElevationData()

In this example, we first define the garminApi type, which includes the getHeartRateData and getElevationData functions. Then, we use a single @module annotation to import the Garmin API and bind it to a value named garminApi.

Finally, we access the getHeartRateData and getElevationData functions directly from the garminApi value.

@cristianoc
Copy link
Author

Now take all the examples in rescript-association/rescript-lang.org#572 (comment) and write them according to this proposal:

// @val
// external setTimeout: (unit => unit, int) => timeoutID = "setTimeout"
let setTimeout: (unit => unit, int) => timeoutID = %raw(`(fn, ms) => setTimeout(fn, ms)`)

// @get external getName: window => string = "name"
let getName: window => string = %raw(`window => window.name`)

// @set external setName: (window, string) => unit = "name"
let setName: (window, string) => unit = %raw(`(window, value) => window.name = value`)

// @get_index external get: (t, string) => int = ""
let get: (t, string) => int = %raw(`(obj, key) => obj[key]`)

// @set_index external set: (t, string, int) => unit = ""
let set: (t, string, int) => unit = %raw(`(obj, key, value) => obj[key] = value`)

// @module("path")
// external dirname: string => string = "dirname"
@module("path") external path: path = "default"
let dirname: string => string = path.dirname

// @new external create: unit => t = "Date"
let create: unit => t = %raw(`() => new Date()`)

// @send external getElementById: (document, string) => Dom.element = "getElementById"
let getElementById: (document, string) => Dom.element = %raw(`(doc, id) => doc.getElementById(id)`)

// @module("library-x")
// @val external doStuff: (@as(json`{format:"utf8", includeStuff: false}`) _, string) => string = "doStuff"
@module("library-x") external libraryX: libraryX = "default"
let doStuff: string => string = (input) => libraryX.doStuff({format: 'utf8', includeStuff: false}, input)

// @val external doSomething: (@ignore 'a, 'a) => unit = "doSomething"
let doSomething: ('a, 'a) => unit = %raw(`(_, value) => doSomething(value)`)

// @obj
// external action: (~name: string, unit) => _ = ""
let action: (~name: string, unit) => _ = %raw(`(name) => ({name: name})`)

// @scope("Math") @val
// external floor: float => int = "floor"
let floor: float => int = %raw(`(value) => Math.floor(value)`)

@cristianoc
Copy link
Author

Screenshot 2023-04-19 at 06 43 03

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment