Wrapping javascript libraries is all consuming

Hi, I’ve been learning purescript (and FP) with a personal project for some time. I love the expressiveness of the language and the well thought out standard libraries. My intuitive test for how good things are is, do I understand how the libraries fit together and can I generally remember where to look for a function? After some careful study, I almost always seem to get it. I’ve really enjoyed my experience.

But one issue I keep running into is that I find myself spending too much time writing typed wrappers for javascript libraries. I’m writing a highly dynamic SPA using javascript libraries like d3 and react-dnd and various graphing/charting libraries and some of them have dense types. For example, the typescript types for the hooks implementation of react-dnd are functions that take records of callbacks that are each passed records of functions that are actually javascript objects that depend on “this” and can return X | Y | Null. Converting these objects into records seems to require that I (javascript) bind them to the object. I have to write all these sum types. Wrapping these functions to convert Nullable to Maybe is another layer of work.

Writing these types is helpful in that I truly understand the library in question when I’m done. If this happened infrequently it wouldn’t be a big deal, but I’ve been experimenting with various libraries and major versions of libraries and it’s turning into a significant portion of the work. Initially I convinced myself that if I were more experienced with purescript it wouldn’t be, but I’m beginning to wonder if that’s true. A good portion of application development is turning into library integration and without a large community to amortize this work over, the problem may persist even if I were better.

Do you have an opinion on the magnitude of this problem?

Is there any work planned to make this easier? (E.g. code generator based on typescript types?)

Are there any good tutorials out there or codebases to study that handle this problem well?

Are there other languages that others have considered that make this easier? (E.g. I don’t BuckleScript/ReasonML nearly as much as purescript, but I’ve read that all the “bs” type annotations make wrapping easier. My impression could be wrong. I’d really hate to give up on types, but a dynamic language like ClojureScript makes interop easier at the expense of having to write verbose clojure specs. I realize there is severe selection bias in asking this question of this community, but where else would you ask?)

Any thoughts are appreciated. Thanks!!

3 Likes

I don’t know of any attempts to translate non-trivial TypeScript definitions to PureScript bindings in a reusable way. The type systems aren’t very compatible, so it requires a lot of encoding (overloaded definitions, anyone?). What TypeScript types as a structural object may not necessarily be what PureScript expects for a record (is this pure data or a referential object?). Trying to handle all cases of JavaScript semantics is very tedious and error prone (this being a huge problem, inheritance, etc). It requires care and often intimate knowledge of what a given library is doing (is it doing sneaky reference checks and adding expandos?). Getting something wrong with a TypeScript type isn’t a big deal since no one expects soundness, but getting it wrong in PureScript can lead to some very unintuitive results, especially with regards to purity and value semantics. Other languages like BuckleScript or ClojureScript don’t expect purity or value semantics, so are therefore more forgiving. By forgiving I mean one cannot make as many assumptions about code since effects can and will happen anywhere.

In my experience, attempts to directly type non-trivial foreign libraries leads to results that are both a chore to maintain and use. When bindings are necessary, my advice is to write minimal and opinionated bindings according to your use case (and PureScript semantics) so you can spend more time writing real code. Otherwise, I would just go ahead and maybe write that component in TypeScript.

8 Likes

i think you have to look carefully at the returns on the efforts taken to wrap JS…sometimes it’s better just to use Purescript in the parts of the program where you really get a lot of benefits from the expressiveness, readability and “making invalid state non-representable” aspects - that can be a pretty big win, depending on the application.

Having sweated thru writing types for (the old row-based) effectful call backs for D3 i think now that it bought me nothing, and i certainly didn’t feel that i’d lost anything when we went to plain Effect on them.

What we’re doing now is to enforce a really strict translation layer (just Purescript types to Purescript-but-really-JS-records) before the FFI. That and some informal rules about trying to keep the computation wherever possible on the PS side of the FFI.

I remain convinced that there is a way of expressing what D3 does as an eDSL that i would like better than D3’s-polymorphic-in-all-the-wrong-ways API but i haven’t done any work on it recently.

2 Likes

Absolutely agree with that last paragraph. That’s dead on, IMO

Great advice. Writing minimal opinionated bindings makes a lot of sense. In practice, I’m assuming this means only converting the fields I need in these wide configuration and callback objects and doing more on the javascript side of FFI to make higher level more specific abstractions. Is more code in the javascript side roughly the net result? I’m doing the former, but not the latter because I was attempting to keep as much code as possible on the purescript side. Are there open examples of applications that have done this well? The only full application I’ve been exposed to purescript-halogen-realworld and that doesn’t use many javascript libraries.

afc, the translation that you describe, PS records to JS “records” is what I’m going for also. Thanks.

Late to the conversation, but I have found this article to be a practical resource regarding strategies for wrapping JavaScript types. The author outlines a strategy for dealing with JavaScript “this” as well.

https://blog.ndk.io/purescript-ffi.html

Clearly this doesn’t speak to the practicality of wrapping dense JavaScript libraries (as opposed to working on the core pieces of your project), but the thread already has some solid advice on that topic.

1 Like