ここ数週間ずっとswiftを書いてきたのですが、swiftは
- 書いていてつまらない
- 言語仕様がちょっと頭おかしい
- オフィシャル言語なのにIDEが糞
- 未来が見えない
と、ぶっちゃけ飽きてしまったので、HaskellでiOSアプリを書くという茨の道に行ってみたいと思いました。 HaskellでiOSアプリがかければ共通部分の処理はHaskellのままAndroidで動かすとか色々できそうです。ぶっちゃけHaskellからCに落としちゃえばどこでも動かせるはずです。というわけで色々探していたところ
というわけで、動くのは動くらしい。少し触ってみて感じを掴んでみることにしました。まずは環境をつくって"Hello, world."です。
今回の参考資料
今回は基本的にこれに従って進めていきます。
ghc-ios/ghc-ios-scripts · GitHub
ロゴマークがカッコいいね。
まずはcabal updateとgit cloneから
最初は参考資料通りでok. cabal updateしましょう。
cabal update && cabal install cabal-install
で、~/.cabal/config
の中のjobs: $ncpus
とそのサブセクションを全部消しましょう。
消したら、git cloneしましょう。
git clone git@github.com:ghc-ios/ghc-ios-scripts.git ~/bin/ghc-ios-scripts
以下では~/bin/ghc-ios-scripts/
にクローンしたものとして話を進めます。パスを通しておいてください。
最初の鬼門
次のステップは簡単。インストール作業はシェルスクリプトにまとまっているので、スクリプトを実行するだけです。もしかするとsudo
が必要です。
installGHCiOS.sh
ただ、これ素直には動かないのでこうしましょう:
$ git diff installGHCiOS.sh diff --git a/installGHCiOS.sh b/installGHCiOS.sh index e13087e..353b7b2 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 -O https://downloads.haskell.org/~ghc/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 -O https://downloads.haskell.org/~ghc/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
URL変わってるやんけ! URL修正したらインストール作業が綺麗にできるはずです。このインストーラは複数のghcをインストールします。具体的には
- ARM用(実機用)ghc
- iOS シミュレータ用(i386の)ghc
の2つです。その時それぞれのghcが違うsettingsを見ることで複数のアーキテクチャ向けのghcを共存させることができます。それぞれのghcは実行バイナリの名前が違うので全部ちゃんと違うコンパイラとしてインストールできます。既存の環境を毀損しません(ここが高度なダジャレになっている)。
Xcodeのワークスペースを作る
Using GHC iOSに書いてあるとおりです。まずはiOS->Application->Single View Applicationを作りましょう。その後の画面では言語はObjective-Cを選びます。
できたらHaskelliOS.xcconfig
ファイルを~/bin/ghc-ios-scripts/
の中からコピーしてプロジェクトに追加しましょう。
ここでひとまずXcodeから離れてコマンドライン作業に戻ります。
コマンドライン作業でHaskellソースをコンパイルする
iOSで実行するHaskellのコードをコンパイルしましょう。Haskellのコードは.a形式にしてObjective-Cのコードから呼び出せるようにします。
つまり、iOS + HaskellはiOSのObjective-Cのコードの骨組みの上にHaskellのコードで味付けをするという感じです。メインコードはHaskellでObjective-Cをグルーとして使うようなイメージですね。今回実行するコードはこちら
{-# LANGUAGE ForeignFunctionInterface #-} module Counter where import Control.Concurrent import Control.Monad foreign export ccall startCounter :: Int -> IO () startCounter :: Int -> IO () startCounter = void . forkIO . void . loop where loop i = do putStrLn (replicate i 'o') threadDelay (10^6) loop (i + 1)
まんま参考資料通りです。見ての通りUI要素を全く使わないアプリになっています。これをCounter.hsとして保存します。
欲しいのはCounter_stub.h
とCounter.a
という2つのファイルです。インクルードファイルとそのメソッドが入ったバイナリです。ファットバイナリなので実機用とシミュレータ用のバイナリが両方ともにCounter.a
に入っています。
ghc-iosでコンパイルする
ghc-ios Counter.hs
でコンパイルしましょう。多分うまく行きません。
なぜghc-iosが通らない?
通らない場合はエラーメッセージをよく見ましょう。例えば
ghc: could not execute: arm-apple-darwin10-clang
の場合、arm-apple-darwin1-clang(GHC for iOSが用意したシェルスクリプト)があるかどうか確認しましょう。。ある場合は無理矢理settingsの中を編集しましょう。settingsは普通は/usr/local/lib/arm-apple-darwin10-ghc-7.8.3
の中にあるはずです。
... ("C compiler command", "/Users/<user name>/bin/ghc-ios-scripts/arm-apple-darwin10-clang"), ...
て感じで無理くりフルパスにしちゃいましょう。
ghc: could not execute: libtool-quiet
の場合は、libtool-quietがghc-iosのシェルスクリプト内で指定されているので
arm-apple-darwin10-ghc $ghcargs -threaded -staticlib -outputdir build/arm -o build/arm/$baseFileName -pgmlibtool /Users/<user name>/bin/ghc-ios-scripts/libtool-quiet $targetFile -stubdir .
て感じでまた無理くりフルパスにしちゃいましょう。i386-apple-darwin11-XXXの方も同じように設定すればおそらく通るはず。通らない場合は自力で解決する他ありません。エラーとにらめっこしましょう。
うまくコンパイルできた
できたらCounter_stub.h
とCounter.a
が生成されているはずなので、プロジェクトに追加しましょう。これでプロジェクトに追加するファイルは以上です。全部終わるとこんな感じ:
main.mがあるかもしれませんが追加するとシンボルがduplicateとか言われるので追加しないでおきましょう。
Objective-C側のコード変更
AppDelegate.m
にすべての変更を適用します。
#import "HsFFI.h"
と#import "Counter_stub.h"
を追加didFinishLaunchingWithOptions
を変更
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { hs_init(NULL, NULL); startCounter(3); // Override point for customization after application launch. return YES; }
これでコードは準備完了です。
プロジェクトの設定を整える
PROJECT -> Build Settings -> Architectures -> Build Active Architecture OnlyをすべてNO
に
Info -> Configurations を両方HaskelliOSに変更
これで基本大丈夫なはずです。
うまくいくとCommand-Rで実機かシミュレータ上で実行できて真っ白な画面とデバッグ表示のところにo
が増えていくのが見えるはずです。
次は
UI要素を使えるようにするかcabal-iosを使えるようにするかに挑戦してみたいです。