はじめに
だいぶ遅れた記事かもしれません。苦しい方法と楽な方法を比較するための例を見せます。その後、更に差が顕著な例を見せます。楽をするために、モジュールData.Function.UncurriedとEffect.Uncurriedを使います。
全体像はコチラ
FFIとは
Foreign Function Interface(外部関数インタフェース)の略で、別のプログラミング言語で定義された関数を呼び出すことを指します。今回の例ではJavaScriptで定義された関数をPureScriptから呼び出します。
苦しい例1
副作用も含んだ例です。JavaScriptのalert関数とprompt関数をFFIから使います。JavaScriptのマニュアル感あふれる高階関数が嫌ですし、こんなことならJavaScriptをそのまま書いたほうが楽です。
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);
};
};
};
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
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
楽な例1
JavaScriptが楽すぎて寂しさすら感じます。
FFI用の関数にはImplを付けるのが慣習っぽいです。
※ Main.pursは変わらないので省略します。
MyFFI.js
"use strict";
// module MyFFI
exports.alertImpl = alert;
exports.promptImpl = prompt;
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
苦しい例2
わかりやすい例です。FFIのためにこんな大変な思いをするのは嫌です。
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;
};
};
};
};
};
};
};
};
};
};
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
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
楽な例2
すごくいい感じです。runFnXX関数が面倒な部分を覆い隠してくれています。
※ Main.pursは変わらないので省略します。
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;
};
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