あと味

たくさん情報を食べて、たくさん発信すると、あとになって味わい深い。

JavaScriptでLispのような再帰的なリストを作るlist関数を作ってみた

Lispの勉強をする際に、まだLisp慣れを全然してないので、JavaScriptで書くとどうだろう?ということを考えることが多々あります。

その勉強方法の良し悪しは置いといて、JavaScriptLispのサンプルプログラムを書いてみようと思った場合、一番ネックなのが、JavaScriptLispでは、リストの考え方がそもそも違うことかなと思いました。

Lisp(1 2 3)というリストを作る時には、以下のようなコードで作ります。*1

(cons 1 (cons 2 (cons 3 '())))

consは第一引数と第二引数から成るセルを作る関数です。

単純に(cons 1 2)というコードをJavaScriptの配列リテラル表記で表すと[1,2]となるでしょうか。

そうすると、先程作りたかった(1 2 3)というリストを作るための上記のコードをJavaScriptの配列リテラル表記で単純に表すと、[1,[2,[3,[ ] ] ] ]となりそうです。*2

Lispのリストについて

JavaScriptの配列リテラルLisp(1 2 3)を表した[1,[2,[3,[ ] ] ] ]は違和感がありますが、Lisp(1 2 3)は、実際は(1 . (2 . (3 . ())))の略なので、JavaScript版も、実はLispのリスト構造に非常に近い表記と言えそうな気がします。

あまり説明できるほど理解していないので、詳しくはSchemeへの道S式とconsセルのページを参照していただくとわかりやすいと思います。

list関数を作ったわけ

以上のことを踏まえて、JavaScriptLispの演習をしてみようと思った場合に*3Lispのリストを簡単に作れる関数が欲しいなと思ったので、list関数を作ることにしました。

インターフェイスはLispのlist関数を模しましたが、Lispのlist関数の引数は最初から再帰的なリストなので、内部的には全然違います。あくまで、インターフェイスを似せただけって話。

list関数(ついでにcons関数も)

function cons(car, cdr) {
  return [car, cdr];
}
function list() {
  function recursive_cons(value, lis) {
    return cons(value, (lis.length > 0) ? recursive_cons(lis[0], lis.slice(1)) : []);
  }
  return (
    (!arguments.length) ? null :
      recursive_cons(Array.prototype.shift.call(arguments), Array.prototype.slice.call(arguments))
  );
}

// list(1,2,3,4,5);
// 結果: [1,[2,[3,[4,[5,[]]]]]]

空リストを[ ]としたのは正しいかどうかわかりません。処理系によっては空リストだし、nilでもあるようです。なので、nullとしても良いかなという気がしましたが、Scheme系で勉強しているので、とりあえず空の配列オブジェクトにしました。nullの方が処理は楽になる気はしてます。

まとめ

JavaScriptでも、この再帰的なリストであることが幸せな場面を見つけられればいいなと思います。木構造のリストが必要な時には役立つのかもしれませんが、まだ見えてません。

とりあえず、cons関数とlist関数があれば、Lispのリスト相当のものを作るのは楽になるのかなと思います。あと、car関数やcdr関数の実装も楽になります。

carをarr[0]として、cdrをarr.slice(1)とすればいいだけかもしれないけど、なんか違うと思う。

*1:'()は空リストという意味

*2:配列リテラル内の空白ははてな記法で変換されるのを避けるためだけのもので、特に意味があるものではありません

*3:「それ[http://www.biwascheme.org/:title=BiwaScheme]でできるよ」ってツッコミはなしで