Skip to content

Foreign function interface

Adam Bergmark edited this page Oct 12, 2013 · 18 revisions

Here's an example of a FFI declaration:

max :: Double -> Double -> Double
max = ffi "Math.round(%1,%2)"

%1,%2,.. corresponds to the arguments you specify in the type.

%* splats all arguments, it will produce %1,%2,.. which can be useful for a declaration ffi "(function (a,b) { return Math.round(a,b); })(%*)"

Be careful when declaring FFI types because Fay can not verify that they are correct.

A FFI function often has side effects, use the Fay monad to represent this:

unixTime :: Fay Int
unixTime = ffi "new Date().getTime()"

You can only use point free style in FFI functions, foo x = ffi "..." x is not allowed. #253

You can also use the FFI on the expression level: add3 :: Int -> Int -> Int -> Int add3 x y z = x + (ffi "%1 + %2" :: Int -> Int -> Int)

Usually you want to access some JavaScript global it's a good idea to always use global access so that a local fay binding won't interfere:

alert :: String -> Fay ()
alert :: ffi "window.alert(%1)"

If you want to access window (browser) or global (nodejs) you can do:

log :: String -> Fay ()
log = ffi "(function () { this.console.log(%1); }).call()"

For Google Closure's advanced optimizations you need to use string access to properties:

jQuery :: String -> Fay JQuery
jQuery = ffi "window['jQuery']"

Records

You can serialize to and from records automatically like this:

data Con = Con Double String

printCon :: Fay ()
printCon = print (Con 1 "str") -- Will output { instance : 'Con', slot1 : 1, slot2 : 'str' }

data Rec = Rec { a :: Double, b :: String }

printRec :: Fay ()
printRec = print (Rec { a = 1, b = "str" }) -- Will output { instance : 'Rec', a : 1, b : 'str' }

getRec :: Fay Rec
getRec = ffi "{ instance : 'Rec', a : 1, b : 'str' }" -- Gives you Rec { a = 1, b = "str" }

Serialization

Here are the rules for serialization. All serialization is based on the type you specify at the ffi declaration, e.g.:

foo :: Int -> String
foo = ffi "JSON.stringify(%1)"

The rules are simple:

  1. Concrete type, e.g. String, Maybe Int: yes, will be de/serialized.
  2. Polymorphic, e.g. a, Maybe a: will not be touched.

There are two helpers for turning on/off serialization for the above:

  1. Ptr Foo: will not be touched.
  2. Automatic a: will attempt to automatically serialize the a at runtime.

There are two utility types for interfacing with JS APIs:

  • Nullable a: Null will be serialized to null.
  • Defined a: Undefined will be serialized to undefined, and will be ommitted from serialized objects e.g. Foo Undefined{"instance":"Foo"}.

Maybe a has different semantics so we couldn't it for these cases. If you want to decode null to the type Maybe (Maybe a) there is no way to tell whether the correct value is Nothing or Just Nothing so we chose to not do special serialization for Maybe. When interfacing with JavaScript this does not matter since its lack of representation for Just Nothing was the problem!

Fay<->Server communication

Please see this comment.

Foreign

In older versions of Fay there was a Foreign type class. This didn't actually do anything in itself, it was just used to add a tiny bit of manually enforced type safety. In new code you can just remove Foreign declarations, it won't impact serialization behavior.