JavaScriptの関数だけで、データ構造を書いたみた

データ構造と関数

今回は、タイトルのとおり、データ構造を関数だけで書いてみたというお話です。この記事では、リスト構造を書くことを目指します。ついでに、条件分岐と繰り返しも関数だけで書いてみましょう。
http://jwodder.freeshell.org/lambda.htmlのメモを参照しています(実証を兼ねて)
言語にJavaScriptを使いますが、基本的なスコープ(スコープチェーンの仕組みなど)を知っていれば初心者でもOKです。

真偽値を関数で表現

まず、手始めに、真偽値を関数だけで書きます。この関数はあとで使います。
trueオブジェクトの代わりに、True関数をコードで書きましょう。同様に、False関数も書いておきます。

    var True = function (t) {
                 return function (f) {
                   return t;
                 };
               };

    var False = function (t) {
                  return function (f) {
                    return f;
                  };
                };

二つの真偽を表す関数の呼び出し方はこうです。

 True(12)(15); // 12
 False(12)(15); // 15

さらに、if文の代用品として、If関数を作ります。

    var If = function(bool) {
               return function(t) {
                 return function(f) {
                   return bool(t)(f);
                 };
               };
             };

それで、これにTrue関数やFalse関数を入れてやると、if文と似たことができます。

 /*条件が真の場合*/
 var s = If (True) (
     function(x) {
       return x;
     }
 ) (
     function(x) {
       return x + 1;
     }
 );

 /*条件が偽の場合*/
 var sa = If (False) (
     function(x) {
       return x;
     }
 ) (
     function(x) {
       return x + 1;
     }
 );

 s(12); // 12

 sa(12); // 13

どういうことかというと、JavaScriptで、関数だけで、条件分岐ができるということです。If関数は、真(True関数)だったら、一番目に入力した関数を返します。偽(False関数)だったら、2番目の関数を返します。
And関数とOr関数はまた別の機会に説明します。

木構造を関数だけで

さらに、Pair関数を作りましょう。この関数は二つのデータを、ペアとして組ませることができる便利なものです。

  var Pair = function (first) {
               return function (second) {
                 return function (bool) {
                   return bool(first)(second);
                 };
               };
             };

Pair関数を使うには、前述のTrue関数とFalse関数を使います。

  var s = Pair(12)(15); // 数値のデータ、12と15の二つをペアとして組んだ
  s(True);  // 12
  s(False); // 15

TrueやFalseだと分かりにくいので、二つのデータを取り出したいときには、次のFirst関数とSecond関数を使います。
ちなみに、First関数は一番目のデータ (上の例では 12)を取り出すことができます

    var First = function (p) {
                  return p(True);
                };
    var Second = function (p) {
                  return p(False);
                };

    var s = Pair(12)(15); // 数値のデータ、12と15の二つをペアとして組んだ
    First(s);             // 12

また、Second関数を使うと、二番目のデータである15を返します。

   Second(s); // 15

これで、木構造も作れますが、今回はリスト構造を作ってみましょう。

リスト構造

このPair関数を以下のように並べていくと、リスト構造を作ることができます。

 var list = Pair( 12 )(
                  Pair( 15 )(
                        Pair( 18 )(
                              Empty
                        )
                  )
            );

Emptyは「もうリストがないよ。中身が空だよ」の意味です。これをlistの前に、関数でコーディングしておきます。

 var Empty = function(x) {
                   return True;
                 };

 var list = Pair(12)/*以下、省略...*/

Second関数を何度も使ってデータを呼び出すと、Second(Second(...と書くようになりますので、Index関数を使います。

    var Index = function (t) {
                  return function (i) { 
                    return First (
                             i(Second)(t)
                           );
                  };
                };

Index関数の引数iには、関数として作っておいた数値を入れれば大丈夫です。たとえば、次のコードのような感じです。

var _0 = function (f) {
           return function (x) {
             return x;
           };
         },
    _1 = function(f) {
           return function(x) {
             return f(x);
           };
         },
    _2 = function(f) {
           return function(x) {
             return f(f(x));
           };
         };

Index(list)(_0); //インデックスが一番目の12を返す
Index(list)(_1); //二番目の15を返す
Index(list)(_2); //三番目の18を返す

空リストについて

リストが空なのか、中身があるのかを見分けられるisEmpty関数をつくります。

   var Empty = function(x) {
                   return True;
                 };

   /*Emptyかどうか判別できるisEmpty関数*/

   var isEmpty =  function(t) {
                    return t (
                      function (x) {
                        return function (y) {
                          return False;
                        };
                      }
                    );
                  };

以下、使い方です。

 isEmpty(Empty); // True
 isEmpty(Pair(12)(15)); // False

つまり、Emptyではないもの、たとえば、Pair関数で作られたものが入力されたとき、isEmpty関数はFalseを返します。Emptyのときには必ずTrueを返すのです。
なぜ、Empty関数が空リストの役目を果たせるのか分かりましたら、次は、これを使って、リストのMap関数を作ってみましょう。

リストに関数を適用させて、新しいリストを作るMap関数

Map関数を作る前に、If関数を改良したSelect関数を作ります。この関数は私が作ったのですが、かなり重要です。

 var Select = function(bool) {
               return function(t) {
                 return function(f) {
                   return bool(t)(f)(bool);
                 };
               };
             };

Select関数は、If関数と比べて、直接、値を返すのではなく、一度、関数を実行してから、その返り値を返すようにします。以下の比較例をご覧ください。

 var t = If (True) (
           12
         ) (
           15
         );

t; // 12

 var s = Select (True) (
           function(x) {
             return 12;
           }
         ) (
           function(x) {
             return 15;
           }
         );

s; // 12

もうひとつ、Select関数とは別に、下ごしらえとして、入力した関数を繰り返しループできるRepeat関数を作ってみます。

    var Repeat = function (f) {

                   /*__f関数は巻き上げ関数を利用したインライン関数
          * コードを見やすくするために、インラインを使っている*/

                   return __f(__f);

                   function __f(x) {
                     return f(
                         function(y) {
                           return (x(x))(y);
                         }
                     );
                   };
                };

使い方は、「ループさせておきたい関数を返すような関数」をRepeat関数に入れれば、OKです。ループの停止条件は、引数itを関数として実行させないようにすることです。

var o = Repeat( function(it) {

                  /*function (n) {...から始まる関数が繰り返される*/

                  return function (n) {
                    if (n < 5) {
                       return n + it(n+1);
                     } else {
                       /*itが実行されていないので、ループはここで停止*/
                       return n;
                     }
                   };
                }
              );

o(1); // 15 = 1 + 2 + 3 + 4 + 5

また、it(m) を実行したとき、その引数 m はループ対象の引数 n へ引き渡されます。
上の例では、if文を使いましたが、次は、Select関数を使うことになります。
さて、これまで作っておいたSelect関数とRepeat関数の二つを使って、Map関数を作りましょう。

    var Map = Repeat (function (it) {
                        /*リピート対象はfunction (f) {...から始まる関数*/
                        return function (f) {
                          return function(t) {
                            return Select( isEmpty(t) )(
                                    /*Trueの場合、この関数が実行される*/
                                    function(x) {
                                      return Empty;
                                    }
                                   ) (
                                    /*Falseの場合、この関数が実行される*/
                                     function(x) {
                                       return Pair (
                                                f (
                                                  First (t)
                                                )
                                              ) (
                                                it(f)(
                                                  Second (t)
                                                )
                                              );
                                     }
                                   )
                          };
                        };
                      });

さっそく、実験です。リストのそれぞれの項目に+1をしてみたいと思います。

var newList = Map(
                function(x) {
                  return x + 1;
                }
              ) (
                list
              );
Index(newList)(_0); // 13 = 12 + 1
Index(newList)(_1); // 16 = 15 + 1
Index(newList)(_2); // 19 = 18 + 1

成功です(Firefox 42.0 Windows 7で確認)。

あとがき

今回は、プログラミングの世界でよく知られている、真偽値、数値、リスト構造などをJavaScriptの関数として表現できることを示しました。すなわち、

  1. 条件分岐や繰り返しなどの制御構造
  2. リストなどのデータ構造
  3. 数値

の三つは、関数として書き直すことができるということです。
もちろん、さらなる検証が求められます。

参考サイト

以下のサイトで学びました。謝意を述べます。ありがとうございました

http://d.hatena.ne.jp/m-hiyama/20070208/1170905900

http://legacy.e.tir.jp/wiliki?%CB%DD%CC%F5%3A%A5%B3%A5%F3%A5%D3%A5%CD%A1%BC%A5%BF%CF%C0%CD%FD%A5%C1%A5%E5%A1%BC%A5%C8%A5%EA%A5%A2%A5%EB

  • 「ラムダ計算をちょっと勉強してみたので、忘れないうちに書いておく」

http://naokirin.hatenablog.com/entry/20120309/1331286179

  • 「『ラムダ計算』を独学で学習するための,講義ノートやPDFのリンク集 (復習用の問題付き)」

http://language-and-engineering.hatenablog.jp/entry/20130313/LambdaCalculusBasicNoteLinks

  • 「6 群(コンピュータ‐基礎理論とハードウェア)-- 2 編(計算論とオートマトン)4 章 チューリング機械」(電子情報通信学会の知識ベース)

http://www.ieice-hbkb.org/files/06/06gun_02hen_04.pdf

http://www.ieice-hbkb.org/files/06/06gun_03hen_04.pdf