once upon a time,

Iris Tradをビール片手に聞くのが好きなエンジニアが、機械学習やRubyにまつわる話を書きます

MeCabのJuliaバインディングMeCab.jlを作りました

Juliaから日本語形態素解析器として最も有名なMeCabを使えるMeCab.jlを作りました。

まだ、METADATA.jlにマージされていないのですが、きっと明日には使えるようになっていると思います。
[2014/09/15 23:33追記] マージされました!

How to use

簡単な使い方は

Pkg.add("MeCab")

を一度していただければ、こんな感じでアクセスできます

using MeCab

mecab = Mecab()

results = parse(mecab, "すももももももももものうち")

for result in results
  println(result.surface, ":", result.feature)
end

JuliaでCのコードをbindingするには

基本的には、公式マニュアルを読めばいいです。

Calling C and Fortran Code — Julia Language 0.4.0-dev documentation

とかだけ書くと大分辛いのですが、ポイントはccallを使えば良いということです。

例えば、以下の様なCのコードがあったとします。(マニュアルより引用)

int main(int argc, char **argv);

すると、Julia側のコードはこう書けばよいのです。

argv = [ "a.out", "arg1", "arg2" ]
ccall(:main, Int32, (Int32, Ptr{Ptr{Uint8}}), length(argv), argv)

問題は、ポインタが帰ってくる場合どうすればいいのかです。
これは、意外と簡単で第二引数をPtr{Void}で受けてあげれば良いです。

返り値が構造体なんかの場合は、対応するimmutableをJuliaで作ってあげて、unsafe_loadすれば良さそうです。

なお、関数ポインタも受けれるとか。
C Structs with function pointers - Google グループ

ただ未確認ですが、構造体のメンバに構造体がいる場合はどうやればいいのかわかりませんでした。
(なので、今回はmecab_node_tは諦めた)

コンストラクタとデストラクタ

MeCabtaggerの様にに、Cで確保したポインタを保持しておく場合、コンストラクタで作りデストラクタで解放するのが良いようです。

bicycle1885さんのこの記事を参考に、実装してみました。
Juliaのデストラクター - りんごがでている

type Mecab
  ptr::Ptr{Void}

  function Mecab(option::String = "")
    argv = split(option)
    if(length(argv) == 0)
      argv = [""]
    end

    ptr = ccall(
      (:mecab_new, "libmecab"),
      Ptr{Void},
      (Cint, Ptr{Ptr{Uint8}}),
      length(argv), argv
    )

    if ptr == C_NULL
      error("failed to create tagger")
    end
    smart_p = new(ptr)

    finalizer(smart_p, obj -> ccall((:mecab_destroy, "libmecab"),  Void, (Ptr{Void},), obj.ptr))

    smart_p
  end
end

ポイントはfinalizerを実装すると、それがデストラクタとして働くということです。

C++は?クラスとかnamespaceとかは?

どうも扱えないようです。
色々と調べたのですが、特にnamespace周りは鬼門のようです。

なので、extern Cをしながらwrapperを一枚書くのが良さそうですね。

最後に

思ったより簡単にバインディングができました。
多分、Kytea.jlも書けそう。

この内容をJuliaTokyo #2でLTしてこようと思います。