Search code examples
mocha.jsmonadsffipurescript

PureScript FFI to mocha


I am trying to write mocha bindings into PureScript and am completely baffled by Control.Monad.Eff

describe(function(){
  //do stuff  
});

Describe is a function that takes nothing and returns IO, or Eff or something that means (side-effect happened no value returned).


My attempts so far

foreign import describe 
  "function describe(n){         \
  \ return function(){           \
  \   window.describe(n); \
  \ };                           \  
  \}" :: forall eff a. Eff eff a -> Eff eff

foreign import describe "describe" :: forall eff a. Eff eff a -> Eff eff
foreign import describe "describe" :: Eff -> Eff
foreign import describe "describe" :: forall eff a. (a -> Eff eff) -> Eff eff

Clearly missing something here. Please help.


Solution

  • The foreign function interface of PureScript is actually very simple. For example suppose you have the following JavaScript function:

    function si(p) {
        return function (r) {
            return function (t) {
                return p * r * t / 100;
            };
        };
    }
    

    You could import it as follows:

    foreign import si :: Number -> Number -> Number -> Number
    

    You could also inline the function as follows:

    foreign import si
        "function si(p) {\
        \    return function (r) {\
        \        return function (t) {\
        \            return p * r * t / 100;\
        \        };\
        \    };\
        \}" :: Number -> Number -> Number -> Number
    

    For side effects PureScript doesn't use the IO monad. Instead it makes use of the Eff monad.

    From what I understand the Eff monad is the same as the IO monad with an extra type parameter: a row of effects.

    For example, in Haskell the print function has the following type:

    print :: Show a => a -> IO ()
    

    In PureScript the print function has the following type:

    print :: Show a => a -> Eff (trace :: Trace | r) Unit
    

    So what do we understand from this?

    1. IO is similar to Eff e where e is a row of effects.
    2. Unit is similar to ().
    3. The print function has the trace effect which is of the type Trace.
    4. In addition, the print function can be combined with an other effect. Row polymorphism. This means that it is composable.

    An Eff value by itself is called an action. For example print "Hello World!" which is of the type Eff (trace :: Trace | r) Unit is an action.

    An Eff value which is an argument to a function is called a handler. It can be thought of as a higher-order effectful function with no parameters.

    An Eff value with no side-effects is known as a pure value:

    type Pure a = forall e. Eff e a
    runPure :: Pure a -> a
    

    Since the row of effects (i.e. e) is polymorphic (or in other words empty, a black hole), PureScript assumes that the function has no side-effects. However it also means that it can be composed with other effectful functions.

    The Eff monad is a contract between the programmer and the compiler in which the programmer promises the compiler that the given Eff value will only have the stated row of effects and no more.


    Coming to your describe function:

    Describe is a function that takes nothing and returns IO, or Eff or something that means (side-effect happened no value returned).

    Actually this is wrong. Your describe function does take a function as an argument:

    describe(function(){
      //do stuff
    });
    

    In addition the function that it takes has no arguments, which means that it is an effectful function. Hence it must be of the type Eff e a where e and a can be any row of effects and any return value respectively.

    Thus your describe function must be of the type:

    describe :: Eff e a -> Eff (describe :: Describe | e) {}
    

    In Haskell it would be written as follows:

    describe :: IO a -> IO ()
    

    PureScript is just more explicit than Haskell. Anyway, Describe is a new effect type that you create which distinguishes it from other effect types such as Trace:

    foreign import data Describe :: !
    

    You would then import describe as follows:

    foreign import describe
        "function describe(f) {\
        \    return function () {\
        \        window.describe(f);\
        \    };\
        \}" :: forall e a. Eff e a -> Eff (describe :: Describe | e) {}
    

    Finally you can use it as follows:

    main = do
        describe $ print "Hello World!"
    

    The entire code is as follows:

    module Main where
    
    import Control.Monad.Eff
    import Debug.Trace
    
    foreign import data Describe :: !
    
    foreign import describe
        "function describe(f) {\
        \    return function () {\
        \        window.describe(f);\
        \    };\
        \}" :: forall e a. Eff e a -> Eff (describe :: Describe | e) {}
    
    main = do
        describe $ print "Hello World!"
    

    It would produce the following JavaScript:

    var PS = PS || {};
    
    PS.Main = (function () {
        "use strict";
    
        var Prelude = PS.Prelude;
        var Debug_Trace = PS.Debug_Trace;
    
        function describe(f) {
            return function () {
                window.describe(f);
            };
        }
    
        var print = Debug_Trace.print(Prelude.showString({})); 
    
        var main = describe(print("Hello World!"));
    
        return {
            main: main, 
            describe: describe
        };
    }());
    

    Hope that helps.