PureScriptのFFIを便利な方法で試す

# はじめに

だいぶ遅れた記事かもしれません。苦しい方法と楽な方法を比較するための例を見せます。その後、更に差が顕著な例を見せます。楽をするために、モジュールData.Function.UncurriedとEffect.Uncurriedを使います。

全体像は[コチラ](https://github.com/matoruru/ffi-purescript)

# FFIとは

Foreign Function Interface(外部関数インタフェース)の略で、別のプログラミング言語で定義された関数を呼び出すことを指します。今回の例ではJavaScriptで定義された関数をPureScriptから呼び出します。

## 苦しい例１

副作用も含んだ例です。JavaScriptのalert関数とprompt関数をFFIから使います。JavaScriptのマニュアル感あふれる高階関数が嫌ですし、こんなことならJavaScriptをそのまま書いたほうが楽です。

```js:MyFFI.js
"use strict";

// module MyFFI

exports.alert = function (x) {
   return function () {
      alert(x);
      return {};
   };
};

exports.prompt = function (x) {
   return function (y) {
      return function () {
         return prompt(x, y);
      };
   };
};
```

```haskell:MyFFI.purs
module MyFFI
   ( alert
   , prompt
   ) where

import Prelude

import Effect (Effect)

foreign import alert :: String -> Effect Unit

foreign import prompt :: String -> String -> Effect String
```

```haskell:Main.purs
module Main where

import Prelude

import Effect (Effect)
import Effect.Console (log)
import MyFFI (alert, prompt)

main :: Effect Unit
main = do
  alert "Hello, world"
  prompt "Input something" "default text" >>= log
```

## 楽な例１

JavaScriptが楽すぎて寂しさすら感じます。
FFI用の関数にはImplを付けるのが慣習っぽいです。

※ Main.pursは変わらないので省略します。

```js:MyFFI.js
"use strict";

// module MyFFI

exports.alertImpl = alert;

exports.promptImpl = prompt;
```

```haskell:MyFFI.purs
module MyFFI
   ( alert
   , prompt
   ) where

import Prelude

import Effect (Effect)
import Effect.Uncurried (EffectFn1, EffectFn2, runEffectFn1, runEffectFn2)

foreign import alertImpl :: EffectFn1 String Unit
alert :: String -> Effect Unit
alert = runEffectFn1 alertImpl

foreign import promptImpl :: EffectFn2 String String String
prompt :: String -> String -> Effect String
prompt = runEffectFn2 promptImpl
```

## 苦しい例２

わかりやすい例です。FFIのためにこんな大変な思いをするのは嫌です。

```js:MyFFI.js
"use strict";

// module MyFFI

exports.add1 = function (a) {
   return a;
};

exports.add2 = function (a) {
   return function (b) {
      return a + b;
   };
};

exports.add3 = function (a) {
   return function (b) {
      return function (c) {
         return a + b + c;
      };
   };
};

exports.add10 = function (a) {
   return function (b) {
      return function (c) {
         return function (d) {
            return function (e) {
               return function (f) {
                  return function (g) {
                     return function (h) {
                        return function (i) {
                           return function (j) {
                              return a + b + c + d + e + f + g + h + i + j;
                           };
                        };
                     };
                  };
               };
            };
         };
      };
   };
};
```

```haskell:MyFFI.purs
module MyFFI
   ( add1
   , add2
   , add3
   , add10
   ) where

import Prelude

import Effect (Effect)

foreign import add1 :: Int -> Int

foreign import add2 :: Int -> Int -> Int

foreign import add3 :: Int -> Int -> Int -> Int

foreign import add10 :: Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int
```

```haskell:Main.purs
module Main where

import Prelude

import Effect (Effect)
import Effect.Console (logShow)
import MyFFI (add1, add10, add2, add3)

main :: Effect Unit
main = do
  logShow $ add1  1
  logShow $ add2  1 2
  logShow $ add3  1 2 3
  logShow $ add10 1 2 3 4 5 6 7 8 9 10
```

## 楽な例２

すごくいい感じです。runFnXX関数が面倒な部分を覆い隠してくれています。

※ Main.pursは変わらないので省略します。

```js:MyFFI.js
"use strict";

// module MyFFI

exports.add1Impl = function (a) {
   return a;
};

exports.add2Impl = function (a, b) {
   return a + b;
};

exports.add3Impl = function (a, b, c) {
   return a + b + c;
};

exports.add10Impl = function (a, b, c, d, e, f, g, h, i, j) {
   return a + b + c + d + e + f + g + h + i + j;
};
```

```haskell:MyFFI.purs
module MyFFI
   ( add1
   , add2
   , add3
   , add10
   ) where

import Prelude

import Data.Function.Uncurried (Fn1, Fn10, Fn2, Fn3, runFn1, runFn10, runFn2, runFn3)
import Effect (Effect)

foreign import add1Impl :: Fn1 Int Int
add1 :: Int -> Int
add1 = runFn1 add1Impl

foreign import add2Impl :: Fn2 Int Int Int
add2 :: Int -> Int -> Int
add2 = runFn2 add2Impl

foreign import add3Impl :: Fn3 Int Int Int Int
add3 :: Int -> Int -> Int -> Int
add3 = runFn3 add3Impl

foreign import add10Impl :: Fn10 Int Int Int Int Int Int Int Int Int Int Int
add10 :: Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int
add10 = runFn10 add10Impl
```

# 参考
- https://pursuit.purescript.org/packages/purescript-effect/2.0.1/docs/Effect.Uncurried
- https://pursuit.purescript.org/packages/purescript-functions/3.0.0/docs/Data.Function.Uncurried
- https://github.com/purescript/documentation/blob/master/guides/FFI-Tips.md