iOSアプリでHaskellで作った関数を使う
Haskellで解くのが楽な問題があってHaskellで書いたけど、その後iOSアプリから使いたくなったので調べてみた。
GHC iOS の導入
まず、GHCやcabal-installを最新にしておく。
brew install ghc cabal-install cabal update && cabal install cabal-install
~/.cabal/config に $ncups という定義があったら消す必要があるらしいが、うちにはなかった。
git clone git@github.com:ghc-ios/ghc-ios-scripts.git ~/bin/ghc-ios-scripts echo -e "\nPATH=~/bin/ghc-ios-scripts:"'$PATH' >> ~/.profile PATH=~/bin/ghc-ios-scripts:$PATH
installGHCiOS.shを実行。クロスコンパイル用のGHCとかを入れてくれる。
installGHCiOS.sh
が、コケたので調べてみたら、curlがリダイレクト処理してなかったので修正。再度実行したらいけた。
diff --git a/installGHCiOS.sh b/installGHCiOS.sh index e13087e..780c326 100755 --- a/installGHCiOS.sh +++ b/installGHCiOS.sh @@ -13,7 +13,7 @@ fi echo "Downloading GHC for iOS devices..." -curl -O https://www.haskell.org/ghc/dist/7.8.3/ghc-7.8.3-arm-apple-ios.tar.xz +curl -OL https://www.haskell.org/ghc/dist/7.8.3/ghc-7.8.3-arm-apple-ios.tar.xz tar xvf ghc-7.8.3-arm-apple-ios.tar.xz && mv ghc-7.8.3 ghc-7.8.3-arm rm ghc-7.8.3-arm-apple-ios.tar.xz cd ghc-7.8.3-arm @@ -35,7 +35,7 @@ rm -r ghc-7.8.3-arm echo "Downloading GHC for the iOS simulator..." cd /tmp -curl -O https://www.haskell.org/ghc/dist/7.8.3/ghc-7.8.3-i386-apple-ios.tar.xz +curl -OL https://www.haskell.org/ghc/dist/7.8.3/ghc-7.8.3-i386-apple-ios.tar.xz tar xvf ghc-7.8.3-i386-apple-ios.tar.xz && mv ghc-7.8.3 ghc-7.8.3-i386 rm ghc-7.8.3-i386-apple-ios.tar.xz cd ghc-7.8.3-i386
iOSアプリプロジェクト作成
Xcodeで新規プロジェクトを作る。
CocoaPodsと協調利用できるか確認したかったからとりあえず適当に1つ入れておく。
platform :ios, '8.0' pod 'SDWebImage' use_frameworks!
HaskelliOS.xcconfigをダウンロードしてプロジェクトに追加。これとCocoaPodsのxcconfigをマージするxcconfigを作成して、向け先もCocoaPodsのものからこちらに向ける。
#include "Pods/Target Support Files/Pods/Pods.debug.xcconfig" #include "HaskelliOS.xcconfig"
#include "Pods/Target Support Files/Pods/Pods.release.xcconfig" #include "HaskelliOS.xcconfig"
HaskelliOS.xcconfigのARCHS = "$(ARCHS_STANDARD_32_BIT)"のコメントアウトを外す(まだ64bitで動かない)。また、HEADER_SEARCH_PATHSも自分の環境に合わせて書き直す。うちの環境ではバージョンだけなおした。
ソースコードの配置
アプリのコードの中にHaskellというフォルダを作って、haskell.hsを追加。試しに以下のようなコードを書く。戻り値はIOでくるむ必要がある様子。
{-# LANGUAGE ForeignFunctionInterface #-}
module HTestApp where
import Foreign
foreign export ccall x3Int :: Int -> IO Int
x3Int :: Int -> IO Int
x3Int x = return $ x * 3!
この状態で、ghc-ios haskell.hsをビルドすると.aとか.hとか出てくるので、Haskellフォルダごとまるっとプロジェクトに追加。

Haskell.hsはターゲットに含めないようにTarget Membershipのチェックを外しておく。
HaskellのコードをiOSアプリビルド時にビルドされるようにする
次に、ビルドしたときにHaskellのコードがビルドされるようにする。ターゲットのBuild PhasesにRun Scriptsを追加して、Compile Sourcesの前に、以下のように追加する
cd $SRCROOT/App/Haskell PATH=$PATH:/usr/local/bin:$HOME/bin/ghc-ios-scripts/ ghc-ios haskell.hs

Haskell部分の初期化と解放
Haskell部分を呼ぶときはhs_initで初期化して、hs_exitで解放する必要がある。ブリッジオブジェクトを作り、こいつの初期化・解放とHaskellの状態が一致するようにした。
#import <Foundation/Foundation.h> @interface HaskellBridge : NSObject - (int)x3Int:(int)x; @end
#import "HaskellBridge.h"
#import "HTestApp_stub.h"
@implementation HaskellBridge
- (instancetype)init
{
self = [super init];
if (self) {
hs_init(NULL, NULL);
}
return self;
}
- (void)dealloc
{
hs_exit();
}
- (int)x3Int:(int)x
{
return (int)x3Int(x);
}
@end
サンプルアプリの作成
あとはこういう画面を作って

こんな感じで使う。
#import "ViewController.h"
#import "Haskell/HaskellBridge.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *textField;
@property (nonatomic) HaskellBridge *bridge;
@end
@implementation ViewController
-(void)viewDidLoad
{
[super viewDidLoad];
self.bridge = [[HaskellBridge alloc] init];
}
- (IBAction)x3Pushed:(id)sender {
self.textField.text = [NSString stringWithFormat:@"%d",
[self.bridge x3Int:[self.textField.text integerValue]]];
}
@end
文字列を渡す
文字列を渡すには、Haskell側でCStringを使って定義しておく。
foreign export ccall repeatString :: Int -> CString -> IO CString repeatString :: Int -> CString -> IO CString repeatString x cstr = do str <- peekCString cstr newCString . foldl (++) "" $ replicate x str
NSStringをcStringUsingEncodingでCの文字列ポインタをとってきてそれを渡せばOK。
- (NSString *)repeatString:(NSString *)str count:(int)count
{
const char *inCstr = [str cStringUsingEncoding:NSUTF8StringEncoding];
const char *outCstr = (const char *) repeatString(count, (HsPtr)inCstr);
return [[NSString alloc] initWithCString:outCstr encoding:NSUTF8StringEncoding];
}
問題点
最初の方にも書いたけど、64bit未対応なので、現時点ではストアにリリースできなくなってしまっていること。