Rescript-compiler: Future work: Introducing a dsl for FFI instead of abusing attributes

Created on 20 Jun 2019  ·  8Comments  ·  Source: rescript-lang/rescript-compiler

basic

external%bs sum : int -> int -> int = "sum"
external%bs bark : string -> unit = "-> bark"



md5-3f5a042f69199bf8b3b31a41e476d2fe



```ocaml
external%bs set : int array -> int -> int -> int = "  [ ] <- "

similar projects: https://github.com/fdopen/ppx_cstubs

APPROVED

Most helpful comment

Fable does something similar with its Emit attribute, but uses templates with placeholders that mimic the actual JavaScript that will be generated instead of cryptic symbols that don't mean anything to anyone. See https://fable.io/docs/interacting.html#emit-attribute

Some examples of what this could look like:

external%bs sum : int -> int -> int = "sum($0, $1)"
let x = sum 1 2  (* Generates: var x = sum(1, 2) *)

(* Instead of [@bs.as {json|...|json}] *)
external%bs cloneNodeDeep : node -> node = "$0.cloneNode(true)"
external%bs attachShadowOpen : node -> node = "$0.attachShadow({ mode: 'open' })"

(* Generalized [@@bs.scope] *)
external%bs locationHref : document -> string = "$0.location.href"

(* Instead of [@@bs.set] *)
external%bs setTitle : document -> string -> unit = "$0.title = $1"

(* Instead of [@@bs.splice] / [@@bs.variadic] *)
external%bs add : collection -> string array -> unit = "$0.add($1...)"

(* Instead of [@@bs.new] *)
external%bs makeRegex : string -> regex = "new RegExp($0)"

All 8 comments

Fable does something similar with its Emit attribute, but uses templates with placeholders that mimic the actual JavaScript that will be generated instead of cryptic symbols that don't mean anything to anyone. See https://fable.io/docs/interacting.html#emit-attribute

Some examples of what this could look like:

external%bs sum : int -> int -> int = "sum($0, $1)"
let x = sum 1 2  (* Generates: var x = sum(1, 2) *)

(* Instead of [@bs.as {json|...|json}] *)
external%bs cloneNodeDeep : node -> node = "$0.cloneNode(true)"
external%bs attachShadowOpen : node -> node = "$0.attachShadow({ mode: 'open' })"

(* Generalized [@@bs.scope] *)
external%bs locationHref : document -> string = "$0.location.href"

(* Instead of [@@bs.set] *)
external%bs setTitle : document -> string -> unit = "$0.title = $1"

(* Instead of [@@bs.splice] / [@@bs.variadic] *)
external%bs add : collection -> string array -> unit = "$0.add($1...)"

(* Instead of [@@bs.new] *)
external%bs makeRegex : string -> regex = "new RegExp($0)"

Indeed, this looks more uniform, but it makes code analyzer more difficult.
From what I can tell, it does not do any analysis or check though.

open Fable.Core
[<Emit("$0 - haha($1 + $2)")>]
let add (x: int) (y: string): float = jsNative
let result = add 1 "2"

generated code

export const result = 1 - haha("2" + null);

We could do some analysis, we will see how much effort it needs

It does check both the JavaScript and template syntax (there's a bit more to it than just placeholders), but does not check the placeholders against the type signature it seems.

Fable is built on Babel which as I understand includes a JS parser, so that part is probably trivial for them to implement. I don't think it's necessary to support the entire JS syntax either, even just to cover the existing FFI surface. The above examples are pretty simple syntactically, especially if arguments are restricted to JSON, which of course you already have a parser for.

Hygienic macros with full support for the entirety of JavaScript's syntax would be pretty neat though.

Idris 1 does something similar too, it's really convenient

@bobzhang is this feature being considered in the current "official" roadmap? Or rather something that contributors could help with? This addition would make BuckleScript way more accessible and expressive for JavaScript developers. ❤️

A more specific question, there seems to be a Flow parser vendored already. (js_of_ocaml also has its own parser as well, based on Menhir).

Is the idea to use the vendored Flow parser for the JS snippets in these expressions?

yeah, it is the road map(after we finish the data representation changes).
The flow parser is more tested (used more).

On Wed, May 6, 2020 at 5:21 AM Javier Chávarri notifications@github.com
wrote:

@bobzhang https://github.com/bobzhang is this feature being considered
in the current "official" roadmap? Or rather something that contributors
could help with? This addition would make BuckleScript way more accessible
and expressive for JavaScript developers. ❤️

A more specific question, there seems to be a Flow parser vendored already
https://github.com/bucklescript/bucklescript/blob/7015ce12d629fd9b6385045e5b486f54a941d956/jscomp/js_parser/flow_ast.ml#L9-L12.
(js_of_ocaml also has its own parser
https://github.com/ocsigen/js_of_ocaml/blob/8263a4cb60bcb6944a71e73626b152fb4f3d1622/compiler/lib/js_parser.mly
as well, based on Menhir).

Is the idea to use the vendored Flow parser for the JS snippets in these
expressions?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/BuckleScript/bucklescript/issues/3618#issuecomment-624312911,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AAFWMK7P6K6SCNTS5API34TRQB7MNANCNFSM4HZRJTHQ
.

--
Regards
-- Hongbo Zhang

I am thinking we can just use the most intuitive way of writing FFIs

external add : int -> int -> int = "function(x,y){
   return x + y
}

Whether inline external or not is up to the compiler, for externals which are hard to inline, we can materialize it:

open struct 
   external add : ..
end 
: sig
   val add : int -> int -> int 
end

Compared with let x = %raw, we get polymorphism for free and can decide whether to inline it or not.
Extra work to do: detect import from third party packages

external add : int -> int -> int = {|
var {fancyAdd} = require('third-party')
function(x,y){
   return fancyAdd(x,  y)
}
|}

My 2c,

I kind of like the existing Bucklescript FFI over this:

external add : int -> int -> int = {|
var {fancyAdd} = require('third-party')
function(x,y){
   return fancyAdd(x,  y)
}
|}

Purescript does something like this, and the FFI turned out to be a much bigger pain point than Bucklescript because of this kind of thing. When I switched to Bucklescript, writing bindings became much less of a burden. It takes longer to write bindings and it's easier to screw something up in the inlined js code, which ends up eating up time debugging things.

I originally thought the proposal here would be more Idris-like, sort of like this:

(* Keep bs.module so that bucklescript can output multiple module systems like commonjs
 * and es6 without it being hardcoded into the app code *)
external add : int -> int -> int = "fancyAdd($0, $1)" [@@bs.module "third-party"]

```ocaml
external add : int -> int -> int = "$0 + $1"

And from @glennsl 's comments, something like:
```ocaml
(* Generalized [@@bs.scope] *)
external locationHref : document -> string = "$0.location.href"

(* Instead of [@@bs.set] *)
external setTitle : document -> string -> unit = "$0.title = $1"
Was this page helpful?
0 / 5 - 0 ratings