Curried vs Uncurried JavaScript for FFI

Compare these two styles of writing a function of two arguments in JS:

exports.runGcdUncurried = function(n, m) {
  return Tg.gcd(n, m);
};

exports.runGcd = function(n) {
  return function(m) {
    return function() {
      return Tg.gcd(n, m);
    };
  };
};

Testing the uncurried function with:

log $ show $ runGcdUncurried 15 20

returns the expected result of 5.

But testing the curried function with:

log $ show $ runGcd 15 20

just returns a text printout of the function body:

function() {
      return Tg.gcd(n, m);
    }

This leads me to believe that JS functions should be written in uncurried style.

But uncurried functions donā€™t always work without errors. For example:

exports.alert = function(msg) {
    return function() {
        window.alert(msg);
    };
};

exports.alertUncurried = function(msg) {
    window.alert(msg);
};

Both of these functions successfully generate the alert popup, but the uncurried function also throws this error in the console:

TypeError: Effect_Alert.alertUncurried(...) is not a function

What is the recommended technique for writing JS functions to be called by PS?

This behavior can be reproduced from this repo with the following commands:

git clone --depth 1 --branch ch10-currying https://github.com/milesfrain/purescript-book.git
cd purescript-book/exercises/chapter10/
spago test
spago bundle-app --to dist/Main.js
# open html/index.html in browser, view dev console, and click "okay" in dialog popups

It is true that all provided examples (runGcd, runGcdUncurried, alert, uncurriedAlert) can be called functions from JS perspective. On the other hand PureScript separates pure functions from ā€œeffectsā€ (side effecting computations) so let me split this answer into two sections.

Uncurried pure JS functions

Data.Function.Uncurried

I think that PureScript by itself doesnā€™t provide the notion of the uncurried function like we know it from the languages like JS. All functions have one argument. Even when you use uncurry you get back a function which takes a single argument - a Tuple :slight_smile: .

If you really want to write type signature for an uncurried JS function and use it through FFI there is a dedicated module in purescript-functions Data.Function.Uncurried for this purpose.

Debugging and examples

runGcdUncurried

Now letā€™s try to understand what is going on when you are running your gcd related examples. There are few surprises there and this is really expected - we are dealing with untyped JS code here - using FFI is a dangerous path to takeā€¦

You are trying to use compiled PureScript function Test.Gcd.gcd (gcd āˆ· Int ā†’ Int ā†’ Int). If you take a look at the output of the Test.Gcd module you can find that this function is compiled into a function which returns a function which returns an Int value:

var gcd = function ($copy_v) {
    return function ($copy_v1) {
      // actual body of the function
      // ...

      return $tco_result;
  }
}

In your FFI you are calling this function in uncurried js manner gcd(n, m). I think that you should use this arguments in two separate calls like gcd(n)(m).

You have provided uncurried runGcdUncurried in your FFI. But you gave it a curried type on the PureScript side:

exports.runGcdUncurried = function(n, m) {
  // This is incorrect usage of gcd :-)
  return Tg.gcd(n, m);
};
foreign import runGcdUncurried āˆ· Int ā†’ Int ā†’ Int

So finally when you applied it to the two Int values runGcdUncurried 15 20 compiler was happy because this is an expected usage of a function with this type. What is even more interesting it worked becauseā€¦ runGcdUncurried called the original gcd function like Tg.gcd(15, undefined) because m was not provided from PS side. This gcd call returned a function which expects another Int which was applied by the compiler and everything finished succesfully.
We got something like Tg.gcd(15, undefined)(20) call at the end.

If you really want to provide a type for this uncurried function you should probably use Fn2 from the lib and run it with runFn2:

exports.runGcdUncurried = function(n, m) {
  // We are using here curried `gcd` function so we need two execute two calls.
  return Tg.gcd(n)(m);
};
foreign import runGcdUncurried āˆ· Fn2 Int Int Int
log $ show $ runFn2 runGcdUncurried 15 20

runGcd

Now letā€™s take a look at runGcd function. You have typed it as crurried function runGcd āˆ· Int ā†’ Int ā†’ Int but there are two problems with the current FFI implementation:

exports.runGcd = function(n) {
  return function(m) {
    return function() {
      return Tg.gcd(n, m);
    };
  };
};

There is additional function() wrapper which is not reflected in the type. If we try to simplify and drop this empty function and use gcd as uncurried function we are going to get a working version:

exports.runGcdFix = function(n) {
  return function(m) {
    return Tg.gcd(n)(m);
  };
};

TBC

6 Likes

Thanks for this explanation.

I now realized that a point of confusion for me was mixing-up currying with the additional function() wrapper.
A better name for alertUncurried would be alertUnwrapped.
After reviewing Ch10 - Representing Side Effects, and the following quote:

an expression of type Effect a should evaluate to a JavaScript function of no arguments

I now see why the alert function, which returns Effect Unit, must be wrapped in a no-argument JS function.

foreign import alert :: String -> Effect Unit
exports.alert = function(msg) {
    return function() {
        window.alert(msg);
    };
};

Yeah. I think that mixing two topics - Effect + curring can be possible source of a confusion. As Iā€™ve promised Iā€™m writing a short part two which only complements the first response with a few links and examples which cover effectful stuff.

Uncurried effectful JS functions

It is quite usual that we want to write a binding for a JS lib which provides effectful functions as a part of its API. It is possible to do this by writing curried wrappers together with function() layer which represents effectful computation. It is also possible to nearly skip this step and provide types for such functions directly using helpers from purescript-effect.

Effect.Uncurried

In this module you can find a set of helpers that allow you to provide a type and execute effectful uncurried JS function without the need of writing all these wrappers on the JS side. The module is really well documented so I wonā€™t repeat the docs hereā€¦ I only want to provide alert* FFI bindings for a quick comparison.

Example alert* bidings

Below bindings use alert as a JS global and builtin function. It is not 100% correct as the alert is a method of the window object. For the correct implementation involving window instance please check purescript-web-html library implmenetation.

Hand written

exports.alert = function(msg) {
  return function() {
    alert(msg);
  };
};
foreign import alert :: String -> Effect Unit

Using the effect library helpers

Even though alert is in the global scope we have to write one line on the JS side because compiler is not happy when foreign module is missing an export.

exports.alert = alert
foreign import alert :: EffectFn1 String Unit

Usage:

runEffectFn1 alert "test"

Usualy when writing a library binding we want to provide a convenient curried version so the user doesnā€™t have to use runEffectFn* functions at all:

exports.alertImpl = alert

foreign import alertImpl :: EffectFn1 String Unit

alert :: String -> Effect Unit
alert = runEffectFn1 alertImpl

Usage:

alert "test"

Iā€™ve updated the example repo accordingly.

2 Likes

As a side note, writing curried functions in JS FFI is pretty clean nowadays with ES6 syntax, e.g.

exports.setDirections = renderer => map => result => () => {
  renderer.setMap(map)
  renderer.setDirections(result)
}
4 Likes

Would having the PS compiler output JS with that alternate curried notation aid with readability? It seems like it would at least cut down on noise and indentation.

If you really want more modern code today, you can run something like https://github.com/lebab/lebab on the output

2 Likes

When I first started learning PureScript as a person coming from the JavaScript world, it helped me to think of Effect as ā€œthunkā€ when dealing with the FFI. So the type Int -> String -> Effect Unit meant I needed a (curried) FFI function which returned a thunk: a => b => ( () => doSomething() ). I donā€™t know why it helped since itā€™s literally the same thing, but yeah :slightly_smiling_face:

2 Likes

Added a book section on this. Let me know if I put any inaccurate info in there.

2 Likes