Search code examples
ffireasonbucklescript

What are the Implications of Different ReasonML External Declarations?


In the examples below, both external declarations achieve the same functionality with slightly different ReasonML function structures.

Does external declaration style impact anything (e.g. performance) beyond ReasonML function structure? Also, does ReasonML have a "suggested" external declaration "style"?

Type Declarations

type dom;
type element;

External Declaration Style 1

[@bs.val] 
external dom: dom = "document";
[@bs.send.pipe : dom]
external get_by_id: string => element = "getElementById";

External Declaration Style 2

[@bs.scope "document"] [@bs.val]
external by_id: string => element = "getElementById";

ReasonML Function Calls

let tag1 = dom |> get_by_id("main");
let tag2 = by_id("main")

Solution

  • The main difference should be apparent from how they're used.

    let tag1 = dom |> get_by_id("main"); // has two parts
    let tag2 = by_id("main") // has only one part
    

    Having two parts means you can swap each part out with other parts, as long as they have the same type signature. We could for example replace dom (the value) with a different instance of dom (the type):

    [@bs.new]
    external make_doc: unit => dom = "Document";
    
    let tag3 = make_doc() |> get_by_id("main");
    

    There's also a more subtle implication to this, however. Having two parts means you can evaluate the parts in different contexts. This isn't as easy to illustrate, but consider this contrived example:

    [@bs.val] 
    external dom: dom = "document";
    
    let dom1 = dom;
    
    [%%raw {|var document = null|}];
    
    let dom2 = dom;
    
    let tag1 = dom1 |> get_by_id("main");
    let tag2 = dom2 |> get_by_id("main");
    let tag3 = by_id("main")
    

    dom1 and dom2 here refer to different values. Using dom1 will work as expected, while using dom2 will crash. As will by_id.

    So which should you use? Well, that depends on what you need. If you will always use the document currently in scope, use bs.scope since that's more convenient. If you don't, or you want to use the same function with several different values, use @bs.send/@bs.send.pipe.