メインコンテンツまでスキップ

PureScriptでFFI

· 約5分

JavaScriptの関数をPureScriptから呼ぶ

定数 Math.PI を呼ぶ

まずは一番簡単なJSの定数から呼んでみます。例としてMath.PIを取り上げます。

同じディレクトリに以下のPureScriptのファイルとJavaScriptのファイルを作成します。

Math.purs:

module Math where

foreign import PI :: Number

Math.js:

'use strict';

exports.PI = Math.PI;

1引数の関数を呼ぶ

次は1引数の関数です。Math.absです。

Math.purs:

foreign import abs :: Number -> Number;

Maht.js:

exports.abs = function(x) {
return Math.abs(x);
};

2引数の関数を呼ぶ

2引数の関数を呼んでみます。

注意点はPureScriptはカリー化されているけど、JavaScriptの関数はカリー化されてないところです。 PureScriptにはカリー化されてない関数をカリー化したり、その逆を行う関数が用意されているので、それを使います。

runFn2 :: forall a b c. Fn2 a b c -> a -> b -> c

ここでは2つの数字を単に足し算する自作のJS関数addをPureScriptから呼んでみます。

Math.purs:

import Data.Function.Uncurried (Fn2, runFn2)

foreign import addImpl :: Fn2 Number Number Number

add :: Number -> Number -> Number
add = runFn2 addImpl

Math.js:

exports.add = function(x, y) {
return x + y;
};

副作用のある関数を呼ぶ

副作用のある関数を呼ぶ場合も注意が必要です。

PureScriptのEffectをJSでどのようの表現すればよいのでしょうか?

以下のようにEffectはJSでは単なる関数で表現されます。

Effect Int
function() {
return 1;
}

これを踏まえて Math.random をFFIしてみます。

foreign import random :: Effect Number
exports.random = function() { // 戻り値をEffectで囲む
return Math.random();
}

引数が1つの副作用のある関数 console.log もFFIしてみます。

foreign import log :: String -> Effect Unit
exports.log = function(message) {
return () => { // 戻り値をEffectで囲む
console.log(message)
};
}

コールバック関数を持つ関数を呼ぶ

コールバック関数を持つ関数をFFIする場合は次の2つの注意が必要です。

  • コールバックはPureScript側で定義されるので、今までとは逆にPureScriptの関数をJSから呼ぶ必要があります。

  • コールバックはたいてい副作用があるのでJS側で副作用を実行する必要があります。

ここでは ボタンをクリックしたときに呼ばれる onClick を例にFFIします。

コールバック関数をJS側から呼び出したときに

callback(event)()

のように最後に副作用を実行する必要があります。

foreign import onClick :: (ClickEvent -> Effect Unit) -> Button -> (Effect Unit)
exports.onClick = function(callback, button) {
return () => { // 戻り値をEffectで囲む
button.onClick((event) => {
callback(event)(); // Effect Unit を実行
});
};
};

2つの引数を持つコールバック関数を持つ関数を呼ぶ

2つの引数を持つコールバック関数の場合は、PureScript側でカリー化されている関数をJS側で普通に呼べるように2つの引数を持つ関数に変換する点に注意が必要です。

変換するには次の関数を使います。

mkFn2 :: forall a b c. (a -> b -> c) -> Fn2 a b c

ここでは forEach を例にFFIします。

foreign import forEachImpl :: forall a. Fn2 (Fn2 a Int Unit) (Array a) Unit

forEach :: forall a. (a -> Int -> Unit) -> Array a -> Unit
forEach f xs = runFn2 forEachImpl (mkFn2 f) xs
exports.forEach = function(callback, xs) {
return xs.forEach((x, i) => {
callback(x, i);
});
};