Ruby の関数と副作用

Haskell の tail 関数を Ruby で次のように実装したが、

def tail(xs) xs.shift; xs end

嫌な予感がしたので試してみた。

irb(main):008:0> tail [1,2,3]
=> [2, 3]
irb(main):009:0> x = [1,2,3]
=> [1, 2, 3]
irb(main):010:0> tail x
=> [2, 3]
irb(main):011:0> x
=> [2, 3]

やはり、引数に実際の配列を渡したときは問題ないが、配列を代入した変数を tail に渡したら、元の変数の値まで変化していた。Ruby の関数では副作用が起きる場合がある。オブジェクト指向言語の場合は、基本的に参照渡しなので、仕方ないことかもしれないが、関数でも副作用が起きる可能性があるのは考慮して置かなければならない。もちろん、Ruby のプログラマはそれも考慮に入れてプログラミングしているので問題は起きないのだろう。

tail のプログラムを次のように変更してみたら、元の配列が書き換えられることはなくなった。

irb(main):018:0> def tail(xs) ys = xs.dup; ys.shift; ys end
=> nil
irb(main):019:0> x = [1,2,3]
=> [1, 2, 3]
irb(main):020:0> tail x
=> [2, 3]
irb(main):021:0> x
=> [1, 2, 3]

しかし、この場合も tail の戻り値の要素がオブジェクトへの参照だと、書き換えたときにxの要素が書き換えられてしまう。

irb(main):026:0> x = [[1],[2],[3]]
=> [[1], [2], [3]]
irb(main):027:0> (tail x)[0] = "*"
=> "*"
irb(main):028:0> x
=> [[1], [2], [3]]
irb(main):029:0> (tail x)[0][0] = "*"
=> "*"
irb(main):030:0> x
=> [[1], ["*"], [3]]

だからと言って、オブジェクトを要素にした配列をいちいち全てコピーしていたら、プログラム言語としては使い物にならなくなる。悩ましいところだ。

Ruby に Haskell をそのまま持ち込むのは無理なようだ。しかし、Haskell 風に配列を処理するプログラムを書くと、前回のエントリーの qsort のように短くて可読性のよいコードを書くことができるので、ケースバイケースで自覚的に使った方が良いだろう。Ruby と Haskell の両方を平行して学習すると、お互いの特徴が浮き立ってきて面白い。

副作用は先刻御承知でマジカルな柔軟なプログラムを書くのか、少々窮屈でもバグのない端正なプログラムを書くのか、それはアプリケーションの用途によって決まってくるだろう。アプリケーションを書かない管理人の場合は、面白ければどちらでもよいのだけれど。

どの言語が優れているかという議論を時々見かけるが、副作用の議論にしても、副作用を積極的に利用する場合があるし、絶対にないほうが良いということはないのではないか。ポインターは毛嫌いされがちだが、C言語の柔軟さはポインターのおかげだし、ポインターはまさに副作用を利用するためのものだ。C言語で少し複雑な処理を書いてみるとわかるが、意外に表現力が豊かだ。低レベルの言語であるにもかかわらず、高級言語並みの効率で大きな処理もできる。それは多くの場合ポインターの力による場合が多い。

仕事でプログラミングをやっている場合は、生産性が問題になるので、できるだけバグを発生させないように、仕様の変更に柔軟に対応できるように、多くの人と共同で開発しやすいようにと言語を選ぶのだろう。しかし、楽しみでプログラミングをやっている場合は、極端に言うとアセンブラレベルのプログラミングでジャンプテーブルを書き換えるような処理が面白かったりする。

いろいろなプログラミング言語には、プログラミング言語を設計した人の美学がこめられているように感じられる。趣味でプログラムを作るときは、その美意識に触れるのが楽しみだ。いろんな言語で簡単なプログラムがどのようにすっきりと、また、応用範囲が広くなるように記述できるのかを見比べると、それぞれの個性があり、言語化できない感動のようなものを感じる。趣味のプログラマーにとっては、それが大きな楽しみの一つだ。

あまり、あれかこれかと決め付けるのは窮屈な感じがする。あれもこれも愉しめばいいのだ。


今日の Haskell
Hugs> replicate 5 '*'
"*****"
Hugs> replicate '*' 5
ERROR - Type error in application
*** Expression : replicate '*' 5
*** Term : '*'
*** Type : Char
*** Does not match : Int

Hugs> flip(replicate) '*' 5
"*****"

flip は関数の引数の順序を入れ変える。map 関数の引数の関数と組み合わせて使うことが多い。
Hugs> map (flip(replicate) '*') [1,2,3]
["*","**","***"]
[PR]
by tnomura9 | 2010-12-08 08:15 | Haskell | Comments(0)
<< Rubyのドット記法 Ruby Haskell 化計画 >>