# RubyからHaskellのコードを呼ぶ邪道な方法(ベンチマーク付)
_published: 2009/02/14_ 
RubyにはRubyの、HaskellにはHaskellの得意分野があるわけでして。RubyからHaskellのコードを呼びたいという需要はあると思います。Cで皮を被せれば呼べるようになる気がしますが、調べるのに時間がかかりそうなので保留。とりあえず、邪道な方法を思いついたので試してみます。
まずは、Haskellのコード。階乗を求める関数です。
```haskell
import System
main = do args <- getArgs
print $ fact $ stoi $ head args
stoi :: String -> Integer
stoi s = (read s :: Integer)
fact :: Integer -> Integer
fact n = factItter n
where
factItter n
| n == 0 = 1
| otherwise = n * factItter (n - 1)
```
Haskell初心者なので、Haskellっぽくなかったらスミマセン。
コンパイル。
```sh
% ghc fact.hs -o fact
```
普通に実行可能ファイルを作ります。
これ呼び出すRubyのコード。
```ruby
def fact(n)
`./fact #{n}`.to_i
end
puts fact(ARGV[0].to_i)
```
バッククォートについては、こちら。
- [Rubyのバッククォート文字列でシェルコマンド実行 - このブログは証明できない。](http://d.hatena.ne.jp/shunsuk/20081223/1230040060)
コマンドの実行結果を文字列で取得できます。
これで、RubyからHaskellのコードを呼び出すことができました。邪道でしょ?
ベンチマーク。引数を変えてやってます。pure haskellはコンパイルしたファイルをそのまま実行。ruby to haskellが上記のRubyスクリプト。pure rubyは下のコード。
```ruby
def fact(n)
if n == 0 then 1 else n * fact(n - 1) end
end
puts fact(ARGV[0].to_i)
```
| | 100 | 1,000 | 10,000 | 100,000 |
| --------------- | ---- | ----- | ---------------- | ------- |
| pure haskell | 0.00 | 0.00 | 0.24 | over 10 |
| ruby to haskell | 0.01 | 0.02 | 2.42 | over 10 |
| pure ruby | 0.01 | 0.03 | SystemStackError | |
あ、pure rubyだとスタックがオーバーフローしたっぽい。再帰じゃなくてループに変更。
```ruby
def fact(n)
a = 1
(1..n).each {|x| a *= x}
a
end
puts fact(ARGV[0].to_i)
```
Haskellのコードも修正しないと、比較になりませんんね。末尾再帰にすればいいのかな?
```haskell
import System
main = do args <- getArgs
print $ fact $ stoi $ head args
stoi :: String -> Integer
stoi s = (read s :: Integer)
fact :: Integer -> Integer
fact n = factItter n 1
where
factItter n x
| n == 0 = x
| otherwise = factItter (n - 1) (x * n)
```
改めて、ベンチマーク。
| | 100 | 1,000 | 10,000 | 100,000 |
| --------------- | ---- | ----- | ------ | ------- |
| pure haskell | 0.00 | 0.00 | 0.27 | over 10 |
| ruby to haskell | 0.01 | 0.02 | 2.46 | over 10 |
| pure ruby | 0.01 | 0.02 | 1.67 | over 10 |
ruby to haskell遅え!
で、みなさんお気づきかと思いますが。文字列をやりとりしてるのがマズそう。10,000の階乗は660桁です。
出力結果として常に0を表示するように修正。
| | 100 | 1,000 | 10,000 | 100,000 |
| --------------- | ---- | ----- | ------ | ------- |
| pure haskell | 0.00 | 0.00 | 0.00 | 0.00 |
| ruby to haskell | 0.01 | 0.01 | 0.01 | 0.01 |
| pure ruby | 0.01 | 0.02 | 0.75 | over 10 |
予想通り!そりゃ速いよね。コンパイルしてるし。いや、このコードがコンパイルで差がでるとは思えない。このへんか。
- [Rubyはなぜ遅いのか?Ruby 1.9は速いのか? - このブログは証明できない。](http://d.hatena.ne.jp/shunsuk/20081023/1224757522)
ここから、おまけ。リストを返したい時は?
```haskell
import System
main = do args <- getArgs
print $ fib $ stoi $ head args
stoi :: String -> Int
stoi s = (read s :: Int)
fib :: Int -> [Int]
fib n = fibItter n 0 [1]
where
fibItter n a xs
| n == 0 = reverse xs
| otherwise = fibItter (n - 1) (head xs) ((head xs + a):xs)
```
evalしちゃいなよ!
```ruby
def fib(n)
eval(`./fib #{n}`)
end
puts fib(ARGV[0].to_i)
```
通常のライブラリのように、関数をひとまとめにできないのが欠点です。文字列が長くなると遅くなるし。でも、HaskellはIO周りがキツイので、そこをRubyで書けると便利だと思うのです。あと、この方法なら、HaskellじゃなくてもOKですね。