PureScript の ffi (2) Maybe 型

モチベーション

以下のような js コードを PureScript から呼び出すことを考えます。

"use strict";  

exports.funcImpl = function(x) {  
  if (x == 0) {  
    return x  
  } else {  
    console.log('0じゃないよ')  
    return  
  }  
}  

つまり、値を返さないかもしれないようなコードです。これを PureScript 側から呼び出すとこんな感じの型にしたいはずです。

module Test where  

import Data.Maybe  

foreign import funcImpl :: Int -> Maybe Int  

しかし、このままでは当然失敗します。

module Main where  

import Prelude  

import Effect (Effect)  
import Effect.Console (logShow)  

import Test  

main :: Effect Unit  
main = logShow $ funcImpl 0  

実行結果

λ spago run  
[info] Installation complete.  
[info] Build succeeded.  
/output/Data.Maybe/index.js:48  
        throw new Error("Failed pattern match at Data.Maybe (line 205, column 1 - line 207, column 28): " + [ v.constructor.name ]);  

何が問題か?

js のコードで返す値が Just で包まれていなかったり、 Nothing じゃないことが問題です。

Maybe の型に取り組む前に、型を foreign import funcImpl :: Int -> Int として動かすとどうなるか確認しておきます

"use strict";  

exports.funcImpl = function(x) {  
  if (x == 0) {  
    return x  
  } else {  
    console.log('0じゃないよ')  
    return  
  }  
}  

module Test where  

import Data.Maybe  

foreign import funcImpl :: Int -> Int  

module Main where  

import Prelude  

import Effect (Effect)  
import Effect.Console (logShow)  

import Test  

main :: Effect Unit  
main = logShow $ funcImpl 0  

実行結果

λ spago run  
0  

ちゃんと値を返している入力に対しては、期待通りの結果となりました。では他の値でも動かしてみます。

module Main where  

import Prelude  

import Effect (Effect)  
import Effect.Console (logShow)  

import Test  

main :: Effect Unit  
main = logShow $ funcImpl 1  

実行結果

λ spago run  
0じゃないよ  
/Users/gupi/Desktop/pures-ffi/output/Data.Show/foreign.js:4  
  return n.toString();  
TypeError: Cannot read property 'toString' of undefined  

やばいですね。実行時エラーになりました。やっぱりどうにかして Maybe 型で返してあげる必要があります。

Maybe 型で返すようする

Maybe 型で値を返すためには、PureScript 側から構成子の JustNothingjs の関数に渡してあげて、それを使って値を返すようにします。

その際 PureScript では func, funcImpl のような名前付けの慣習があるようです。

コード全体はこんな感じです。

"use strict";  

exports.funcImpl = function(just, nothing, x) {  
  if (x == 0) {  
    return just(x)  
  } else {  
    console.log('0じゃないよ')  
    return nothing  
  }  
}  

module Test where  

import Data.Maybe  
import Data.Function.Uncurried (Fn3, runFn3)  

foreign import funcImpl :: Fn3 (Int -> Maybe Int) (Maybe Int) Int (Maybe Int)  

func :: Int -> Maybe Int  
func = runFn3 funcImpl Just Nothing  

module Main where  

import Prelude  

import Effect (Effect)  
import Effect.Console (logShow)  

import Test  

main :: Effect Unit  
main = do  
  logShow $ func 0  
  logShow $ func 1  

実行結果

λ spago run  
(Just 0)  
0じゃないよ  
Nothing  

できた!

感想

最近やっとPureScriptの雰囲気に慣れてきた。Halogen使って遊ぼう。

ちなみに参考にしたドキュメントによると、以下のような書き方は非推奨です。

exports.doSomethingImpl = function(fn, x) {  
  if (fn(x)) {  
    return Data_Maybe.Just.create(x);  
  } else {  
    return Data_Maybe.Nothing.value;  
  }  
};  

参考リソース