PureScript Object Oriented Foreign Function Interface is a collection of helper functions for binding into foreign OO style apis
Lets say we want to bind into the following object, defined with foreign data type Obj
.
var obj = {
val : 0,
add : function(x, y,) {
return x + y;
}
inc : function(){
obj.val++;
}
};
FFI alone is quite a bit of boilerplate:
MyModule.js
:
exports.add = function (obj) {
return function(x) {
return function(y) {
return obj.add(x, y);
};
};
}
exports.inc = function (obj) {
return function() {
obj.inc();
};
}
MyModule.purs
:
foreign import data Obj :: *
foreign import data MyMutable :: !
foreign import add :: Obj -> Number -> Number -> Number
foreign import inc :: forall e. Obj -> Eff (myMutable :: MyMutable | e) Unit
Object Oriented Foriegn Function Interface provides functions to handle boilerplate when binding onto JavaScript objects.
MyModule.purs
:
foreign import data Obj :: *
foreign import data MyMutable :: !
add :: Obj -> Number -> Number -> Number
add = method2 "add"
inc :: forall e. Obj -> Eff (myMutable :: MyMutable | e) Unit
inc = method0Eff "inc"
Much neater. Here's how it works:
method2 :: String -> objectWithMethod -> firstArgToMethod -> secondArgToMethod -> returnValue
The String
in the first argument is name of the property you are binding to.
method
bindings are in the following format method<number of arguments>
for pure functions and method<number of arguments>Eff
for Effectful functions.
Mutable properties are also assisted.
var foo = { bar : 0 };
foreign import data Foo :: *
foreign import data MyMutable :: !
getBar :: forall e. Foo -> Eff { myMutable :: MyMutable | e } Number
getBar = getter "bar"
modifyBar :: forall e. Foo -> (Number -> Number) -> Eff { myMutable :: MyMutable | e } Number
modifyBar = modifier "bar"
setBar :: forall e. Foo -> Number -> Eff { myMutable :: MyMutable | e } Number
setBar = setter "bar"
main = do
b <- getBar foo
b' <- setBar foo 1
modifyBar foo \x -> x + 1
b'' <- getBar foo
assert $ b == 0
assert $ b' == 1
assert $ b'' == 2
Shortcuts for the Instantiation of Object Oriented Classes is supported on two layers.
function Foo(x){
this.x = x;
}
foreign import data Foo :: *
foreign import data NewFoo :: !
newFoo :: forall a e. a -> Eff (foo :: NewFoo | e) Foo
newFoo = instantiate1 "Foo"
This will pull Foo
off the the global context (window
or global
or self
) like so new window["Foo"]
. The pattern here is instantiate<number of arguments>
If the library you are binding to holds its Object Oriented Classes inside a NameSpace like this:
var Lib = {
Foo : function(x){ this.x; }
};
foo = new Lib.Foo(0);
Then use the instantiateNFrom
functions
foreign import data Foo :: *
foreign import data Lib :: *
foreign import data NewFoo :: !
foreign import lib "var lib = Lib" :: Lib
newFoo :: forall a e. a -> Eff (foo :: NewFoo | e) Foo
newFoo = "Foo" `instantiate1From` lib
The pattern here is instantiate<number of arguments>From
.
Lets say you wish to bind to something on the global context. For example:
context.setTimeout(fn, milliseconds);
Helper methods are provided with patterns:
method<number of arguments>C
method<number of arguments>EffC
so you can bind like so:
timeout :: forall e a. Eff (timer :: Timer | e) a -> Milliseconds -> Eff (timer :: Timer | e) Timeout
timeout = method2EffC "setTimeout"
where C
indicates Global Context (window
or global
or self
).
This also works for getters and setters.
context.foo = 3;
getFoo :: forall e. Eff e Number
getFoo = getterC "foo"