I'm trying to wrap a Javascript library where I have a method A.bar(f)
which takes as parameter a function f : B -> void
.
Since I'd like to use bar
to perform some anynchronous computations, on the Purescript side, I have a function declaration
foreign import foo :: Fn2 A (B -> Aff Unit) -> EffectFnAff Unit
In its corresponding Javascript file, I have something like
exports.foo = function (a, f) {
return function (onError, onSuccess) {
a.bar(function (b) {
f(b)
})
return function (cancelError, cancelerError, cancelerSuccess) {
cancelerSuccess()
}
}
}
The issue I have is that f(b)
is an Aff
object and I don't know how to execute it on the Javascript side.
Accessing PureScript data structures from FFI-ied JavaScript is always a bad idea. You're relying not only on the specific way the library is written (without compiler support to catch errors!), but also on the compiler itself, for the runtime representation may change from one compiler version to another (note that this doesn't apply to EffectFnAff
, because it's explicitly intended for FFI and carefully defined as such, in terms of EffectFn2
).
The way to represent an effectful computation in FFI is via Effect
:
foreign import foo :: Fn2 A (B -> Effect Unit) -> EffectFnAff Unit
Such function can now be called from JavaScript the way you do it - as f(b)
.
And if you want the consumer of your library to provide Aff
, what you do is make a wrapper:
foreign import foo_ :: Fn2 A (B -> Effect Unit) (EffectFnAff Unit)
foo :: A -> (B -> Aff Unit) -> Aff Unit
foo a f = fromEffectFnAff $ runFn2 foo_ a (launchAff_ <<< f)
Then you just export the wrapper foo
, but not the FFI import foo_
.
On a somewhat related note, I would also recommend doing away with EffectFnAff
, because you're not actually launching anything asynchronous, but are always calling cancelerSuccess()
.
So instead, I would recommend this:
// JavaScript
exports.foo = (a, f) => a.bar(f)
-- PureScript
foreign import foo_ :: EffectFn2 A (B -> Effect Unit) Unit
foo :: A -> (B -> Aff Unit) -> Aff Unit
foo a f = liftEffect $ runEffectFn2 foo_ a (launchAff_ <<< f)
The wrapper still has Aff
in both plces - this is assuming that you need the whole thing to fit into Aff
for your own reasons. Otherwise it could be just foo = runEffectFn2 foo_