スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Cなどで書かれたプログラムからHaskellを使う(メモ)+Xcodeのプロジェクトに組み込むときのメモ

ある種の処理を書く時にHaskellは便利だが、プログラムの他の部分はCなどの他の言語で書きたい場合がある。 そこで、Haskellで動的ライブラリ(.soや.dylib)を作り、他のプログラムから呼び出せるようにしたいと思った。

Haskell側ではforeign exportで関数をエクスポートすればよい。 foreign exportの入ったモジュールをコンパイルすると*_stub.h,*_stub.cというファイルが生成される…と思ったら現在のGHCでは_stub.cの方は生成されない?

C側のプログラムからは、生成された_stub.hを#includeすればHaskellで書かれた関数のC言語でのプロトタイプが見える…のだが、この_stub.hからはHsFFI.hというHaskell処理系の(?)ヘッダで、単体のライブラリとして配布する際に依存性があると邪魔だ。なので、自前でヘッダファイルを書くか、Haskellで書かれた関数をラップする関数をCで書くかするといいと思う。

また、Haskellの関数を使う前にHaskellのランタイムを初期化する必要がある。 Calling Haskell from C - HaskellWiki8.2. Using the FFI with GHCを参考に初期化ルーチンを書こう。 __attribute__((constructor))__attribute__((destructor))をつけておけばライブラリの使い手でいちいち初期化ルーチンを呼ぶ必要がなくて楽かもしれない。

ライブラリのコードとしてはCのコードとHaskellのコード両方が含まれる。どっちもまとめてGHCに渡してしまうといいだろう:

ghc -O2 --make -no-hs-main -optl '-dynamiclib' -o libHogehoge.dylib Hogehoge.hs Higehige.c

当初試したときは、リンク時に_environが見つからないというようなエラーが出たが、この問題はGHCの最新バージョンでは直っているらしく、Haskell Platform 2012.4(GHC 7.4.2)をインストールしたら解決した。

自分の場合は、ライブラリを呼ぶ側のプログラムはCocoaアプリケーションで、Xcodeで開発している。それで、Xcodeのビルドボタンを押すとHaskellのライブラリも一緒にビルドされるようにしたい。 ビルド中に上記のコマンドラインが実行されるようにしたいのだが、Haskell用のBuild Ruleを追加して…というのは難しそうだったのでMakefileを書いてExternal Build Systemとしてターゲットを作った。

Makefileの内容はこんな感じで:

PRODUCT = $(BUILT_PRODUCTS_DIR)/libHogehoge.dylib

all: $(PRODUCT)

$(PRODUCT): Hogehoge.hs Higehige.c $(BUILT_PRODUCTS_DIR)
	ghc -c -O2 --make -outputdir $(DERIVED_FILE_DIR) -o $@ Hogehoge.hs
	ghc -O2 --make -no-hs-main -optl '-dynamiclib' -outputdir $(DERIVED_FILE_DIR) -o $@ Hogehoge.hs Higehige.c
	install_name_tool -id @executable_path/libHogehoge.dylib $@

$(BUILT_PRODUCTS_DIR):
	mkdir -p $@

$(BUILT_PRODUCTS_DIR)$(DERIVED_FILE_DIR)はXcodeがコマンドを呼び出す際に設定してくれる環境変数。 Higehige.cから_stub.hを参照する場合、.hsと.cを一度にghcに渡すと_stub.hが存在しない場合エラーになるようで、先に.hsだけをコンパイルして_stub.hが確実に存在するようにしている。なんだか無駄なようで、自分のやり方が悪いのかもしれない。 ライブラリを.app内に含めることを考えて、リンク後にinstall_nameを設定している。

Xcode側の設定は、External Build Toolとして作ったターゲット(仮にHigehigeとする)の、DirectoryにMakefileがあるディレクトリを設定しておく。ソースコード内の位置指定に$(SRCROOT)が使える。 また、プロジェクトのファイルとして、生成されるlibHigehige.dylibを追加しておく。ビルドしてできたファイルをXcodeに追加して、プロパティーのLocationをRelative to Build Productsに設定すればいい感じになるだろう。

主となるアプリケーションの設定としては、Build PhasesのTarget DependenciesにHigehigeを追加しておく。 それから、先に追加したlibHigehige.dylibをリンクするように設定する。 あと、ビルド後にlibHigehige.dylibをアプリケーションバンドル内にコピーするBuild Phaseを追加しておく。

我ながらかなり読みにくい文章になってしまった。CからHaskellを呼ぶ話と、XcodeでExternal Build Toolを使ってファイルをビルドする話が混ざっている気がする。 しかし、読みやすい文章を心がけたりスクリーンショットを載せたりしてがんばると、ただでさえ半年間更新していないブログの更新がますます億劫になってしまうのは間違いない。だからこのぐらいの適当さでいいのだ、ということにしておく。

スポンサーサイト

テーマ : プログラミング | ジャンル : コンピュータ

Listモナドで非決定的計算をしよう

Haskellでは、Listモナドを使うことにより、非決定的計算ができるらしい。どういうことか?

実数xの平方根±√xについて考えてみよう。±√xはxの値によってありうる値が異なる。

  • x>0: 2個の値(±√x)
  • x=0: 1個の値(0)
  • x<0: なし

これをHaskellで表現するとこうなる:

sqrt' :: RealFloat a => a -> [a]
sqrt' x | x >  0 = [sqrt x,-sqrt x]
        | x == 0 = [0]
        | x <  0 = []

このsqrt'を使って2次方程式 a*x^2+b*x+c=0 の解を返す関数を定義する:

import Monad
solution :: RealFloat a => a -> a -> a -> [a]
solution a b c = ((-b)+sqrt'(b^2-4*a*c))/(2*a)
  where
    (+) = liftM . (Prelude.+)
    (/) = flip (liftM . flip (Prelude./))

これだけで、この関数solutionはあり得る解を全て返すようになる。試してみよう。

*Main> solution 1 0 1       -- x^2+1=0
[]
*Main> solution 1 (-2) 1    -- x^2-2*x+1=0
[1.0]
*Main> solution 1 (-1) (-1) -- x^2-x-1=0
[1.618033988749895,-0.6180339887498949]
*Main> 

複素数の場合はsqrtは常に値を持つので、±を定義すればお馴染みの公式をHaskellで表現できる。

infixl 6 ±
(±) :: Num a => a -> a -> [a]
a ± b | b /= 0 = [a+b,a-b]
      | b == 0 = [a]

csolution :: Floating a => a -> a -> a -> [a]
csolution a b c = ((-b)±sqrt(b^2-4*a*c))/(2*a)
  where
    (/) = flip (liftM . flip (Prelude./))
*Main> import Complex
*Main Complex> (csolution 1 0 1)::[Complex Double]   -- x^2+1=0
[0.0 :+ 1.0,(-0.0) :+ (-1.0)]
*Main Complex> (csolution 1 1 1)::[Complex Double]   -- x^2+x+1=0
[(-0.5) :+ 0.8660254037844386,(-0.5) :+ (-0.8660254037844386)]
*Main Complex> 

# U+2062 INVISIBLE TIMESが演算子として使えればもう少しコードがきれいになるのにな…
# つーかこの例は非決定的計算の例として適当なのだろうか

テーマ : プログラミング | ジャンル : コンピュータ

Haskell の Parsec で JavaScript パーサを書いた

前にも書いたようにJavaScriptによるJavaScriptのパーサを書いてたんだが、JavaScriptでは速度にも記述性にも限界を感じ、HaskellでParsecを使い書き直す事にした。Haskellには代数データ型もパターンマッチもあるので、パース後のASTを変換するのもHaskellの方がやりやすいはずだ。また、静的型付けなのでバグも減るだろう。

というわけでParsecで(自分にとって初めての、Parsecを使う実用目的の)パーサを書いた。JavaScriptでは改行が意味を持つので、改行を読んだかどうかをパーサの状態に持たせる事にした。そのためにはwhiteSpace相当のものを自分で実装する必要がある。その場合、Text.ParserCombinators.Parsec.Tokenを使うことはできないので、Text.ParserCombinators.Parsec.Tokenにあるような関数を自作した。

いくつかのファイル(数百行)をパースしてみたが、意外に時間が掛かる。プロファイルをとってみるとwhiteSpaceで時間が掛かっているようだ。JavaScriptで書いていた時も字句解析の部分に時間が掛かっていた気がする。

テーマ : プログラミング | ジャンル : コンピュータ

HJScriptを使ってみる

HJScriptというライブラリを使うと、HaskellのDSLとしてJavaScriptを書けるようだ。Haskell Server Pagesの一部らしい。

使ってみようと思うが、ドキュメントが皆無だ。以下、試しに書いてみたコードと実行結果:

import Prelude (IO,($),Show(show),putStrLn)
import HJScript
import HJScript.Lang
import HJScript.DOM.Window
import Language.HJavaScript.Syntax

data Navigator = Navigator deriving Show
instance IsClass Navigator

navigator :: Exp Navigator
navigator = JConst "navigator"

userAgent :: Exp Navigator -> JString
userAgent = deref "userAgent"

mainJS :: HJScript ()
mainJS = window#alert (JString "userAgent:" .+. navigator#userAgent)

main :: IO ()
main = putStrLn $ show mainJS
$ runhaskell hjstest.hs
window.alert('userAgent:' + navigator.userAgent);

テーマ : プログラミング | ジャンル : コンピュータ

メモ化の代わり

Haskellだと常にメモ化されると思ってたらそうじゃないらしい。
ググってみたらStateモナドでメモ化してるのを見つけたが、Stateモナドとか良く分からん。
そのうち、今の用途(数列)なら無限リストで十分なことに気づく。
階乗だとこんな感じになんのかな。
fact = 1 : zipWith (*) fact [1..]

テーマ : プログラミング | ジャンル : コンピュータ

プロフィール

minoki

Author:minoki
好きなプログラミング言語:
Haskell,Lua
GitHubアカウント
Twitter

最新記事
月別アーカイブ
カテゴリ
検索フォーム
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。