RFC: simplifications about bindings

Bindings are one of the most difficult parts for beginners, recently I made some clean up on how externals are handled and think it could be significantly simplified. This comes with two enhancement, the first one is a minor enhancement, the second one is a major one. This proposal should be backwards compatible

  • First enhancement , no redundant attributes like @val

Previously, for a simple binding, user has to add an attribute, this does not need any more

@val
external getElementById : string => dom = "getElementById"

let h = getElementById("x")

Without @val, this should just work:

external getElementById : string => dom = "getElementById"

let h = getElementById("x")
  • Second enhancement: we can accept more kinds of payload in the string literals:
external add : (float,float) => float = "(a,b)=> a+ b"

User can write any valid JS function inside the string literal, since it is plain JavaScript, so in theory, user can build any bindings without a second thought. Thanks to ES6 syntax, this could be very short. We will do as much check as we can, optimisations can be added later

28 Likes

The second one is also achieved by using raw. What would be the difference between these two approaches?
I guess external would be more readable than raw.

1 Like

Seems like a great incremental improvement. Thanks!

2 Likes

Less context switch, all bindings go to external.

We also want to encourage this style. Based on my observation, users waste too much time in creating a beautiful binding when it can be easily done in a couple of lines of raw code, I consider this as a pre-mature optimization.

Some minor differences:

  • external can be inlined when it is possible, we may spend more time on it in the future
  • It is more polymorphic friendly, currently for raw, it can not be polymoprhic
  • raw makes type annotation optional which is a bad thing, it is recommended to always annotate externals
10 Likes

It will be important to make it clear in the docs what is preferred in terms of the keywords. For example, @module still seems useful since the compiler will emit different kinds of modules depending on configuration, but maybe others like @new should be deemphasized and instead encourage writing the JS in the string.

Will the JS string be parsed and checked like in raw?


These changes would greatly simplify some real life more complex interop, like sgrove posted here. Then he could bind easily with something like this (if I understood correctly):

type audioContext

external createAudioContext: unit => option<audioContext> = "() => {
  let AudioContext =
    typeof AudioContext !== "undefined" ? AudioContext : 
    typeof webkitAudioContext !== "undefined" ? webkitAudioContext :
    typeof mozAudioContext !== "undefined" ? mozAudioContext : null

  return !!AudioContext ? new AudioContext() : null
}"
2 Likes

This looks great, and makes a lot of sense. The second enhancement in particular looks quite powerful! Looking forward to putting that one to use.

1 Like

I support this motion of simplifying bindings since it is still my main friction in addition to promises.

I would think that all the @val should just be referred on a global Js like Js.Document.getElementById instead of using externals.

3 Likes

I’m all for this, @val always seemed a bit weird. I really like the second, I always get weird unused argument errors when I try to do that in a raw block.

I am a new comer to ReScript and i am struggling a bit with binding. It’s good to know that i can forget about one of the binding directives.
And the second one is really great. Looking forward to it.
Thank you for keep improving the language.

3 Likes

Will the JS string be parsed and checked like in raw ?

Yes

These changes would greatly simplify some real life more complex interop, like sgrove posted here .

Yes, pretty much most things you can do, I will optimize some common patterns to make it zero-cost like what we had. The main motivation is to reduce the learning complexity of current various attributes.

2 Likes

I would think that all the @val should just be referred on a global Js like Js.Document.getElementById instead of using externals.

Can you elaborate a little bit?

1 Like

well we could have all the DOM document methods in Js.Document and all DOM Window methods in Js.Window so we would call for example setTimeout like Js.Window.setTimeout(()=>Js.log("hi"),1000)

That way there would be no need to write bindings to all the globals and just leave the bindings for libraries.

The drawback could be library bloat?

1 Like

Do you know that there is a Js.Global namespace?

Having a complete builtin set of web api bindings was the reason why bs-webapi was started as an incubator project. Unfortunately the experiment has shown that having general bindings for the whole webapi is too ambitious and probably not worth generalizing. That’s why we encourage writing your own specific bindings for more complex use cases in the first place.

Currently the Js module provides abstract types other libraries can rely on, so we can at least allow interoperability between libraries. It also comes with a few global functions that were easy to implement, such as setTimeout.

Anyways, this is more a question about Stdlib functionality and kinda unrelated to the feature proposed above, I think.

2 Likes

Ah yes! I keep forgetting because it’s hard for me to remember which methods are included in Global since it’s just a small set of all available in the real global.


regarding the decorators, personally I think that the names are a bit too generic (or abstract?). For example:

@val seems to me like it declares a value in a variable. I would rename it as @global
@send I would like it sends a message or some XHR. Maybe I would alias it as @method or @callor @apply or @run

@new this one does seem straight forward
@module also self-explanatory
@set makes sense
@get also ok for me

1 Like

@apply and @call are very ambiguous too in the context of JS. @run just doesn’t tell you much, which leaves us with @method. But is the change worth it?

Also, @send makes sense, I guess, if you happen to know that the original idea of OOP was message passing :grin:

3 Likes

I would create an alias instead of changing it.

I didn’t know this! But anyways you are not always sending data when you use @send. It’s more like to bind a method. So I would say that @method is more intuitive for my persona as a web dev with JS as being my first language and no CS degree.

No, I’m a web dev with no CS degree too, and I agree that @method is probably more obvious

1 Like

I guess only smalltalk / objective-c (?) ppl will know why it’s called “send” :joy:

… the term grew on me though

4 Likes