最近の更新 (Recent Changes)

2014-01-01
2013-01-04
2012-12-22
2012-12-15
2012-12-09

Wikiガイド(Guide)

サイドバー (Side Bar)


← 前のページに戻る

5. 三式言語: クロージャを持つプログラミング言語

5.1 クロージャを持つプログラミング言語とは

三式言語はクロージャを持つプログラミング言語になるように改造しましょう。

さて、クロージャです。

クロージャとは何でしょうか。

クロージャを持つプログラミング言語には、Javascriopt, Perl, Python, Ruby, scheme, common Lisp, Smalltalk, Scala などがあります。

クロージャも純関数型のときと同じで、第一級関数が必要不可欠とか、高階関数が使えないととか、無名関数とかラムダ関数が欲しいとか難しい話になることが多い気がします。 でもそれらは実は本質とは思えません。クロージャや純関数と組み合わせると、とても便利だよという意味と思って今はそれらについては忘れてください。 後の章でそれらについても説明して実装した言語を作る予定です。

クロージャとは何でしょうか。

ウィキペディアのクロージャ から引用してみましょう。

「クロージャ(クロージャー、closure、閉包)はプログラミング言語における関数オブジェクトの一種。引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決することを特徴とする。関数とそれを評価する環境のペアであるともいえる。 」

なんか難しそうな表現ですね。

一つづつキーワードを見ていきましょう。まず「閉包」というのが目につきます。何かを包んで閉じている感じですね。

次に「関数オブジェクト」とあります。これは単なる関数だけではなく「引数以外の変数」つまり関数内のローカル変数を含めたものを関数オブジェクトと呼んでいるようです。

ただし、変数は、「自身が定義された環境(静的スコープにおいて解決すること)」だと書いてあります。 クロージャの中のローカル変数はC言語などの手続き言語のローカル変数とは異なります。 何が異なるかというと、普通の手続き型言語のローカル変数は、関数が呼び出されると生成され関数が終了すると破棄されます。 しかし、クロージャを持つ言語の中のローカル変数は、一度生成されると関数が終了しても破棄されずに残ります。

どのような仕組みかといいますと、クロージャではない普通の言語では、関数が呼ばれるとスタック上にフレームを拡張してローカル変数の領域が作成されます。関数が終了するとスタック上のローカル変数が破棄されてスタック上のフレームが縮小されます。 関数が呼ばれるたびに新しいローカル変数が生成され、終了とともに破棄されます。

クロージャの言語ではヒープ上にローカル変数が作成されます。関数が呼ばれるとヒープ上のローカル変数値を参照・更新します。関数が終了してもヒープ上のローカル変数は残っています。

つまり、クロージャでは、関数とローカル変数は常に一対でありそれを「環境のペア」と表現しているのです。 言い換えると、関数と常に対応しつつ消えることのないローカル変数の組み合わせを関数オブジェクトと呼び、それがクロージャの正体なのです。

では、クロージャでは何が良いのでしょうか?メリットは何でしょうか?

純関数とはまったく逆です。ローカル変数が常に更新されてしまう可能性があります。だから参照透過性なんてありません。

では、どうして?

クロージャでは、関数内に常に変更可能な値を持つ、つまり関数が状態を持つことが可能になるのが一番のメリットなのです。

その関数オブジェクトの中のローカル変数に関数へのポインタを設定したら、それはその関数オブジェクトのメソッドとして使え、プロトタイプベースのオブジェクト指向に繋がるのですがそれは後の章の話としたいと思います。

それでは次の例題を見てください。


function f();
begin
        var n;

        print n;
        n = n + 1;

end

begin
        f();
        f();
        f();
end

関数fは、変数nの値をプリントして+1しています。 メインルーチンでは、f()を三回呼び出しているだけです。

この例題を三式言語でコンパイルして実行すると次のようになります。


0
1
2

同じ関数fを3回呼んでいるだけなのに、値が0, 1, 2 と増えていきます。

これが、関数が状態を持つということの証といえるでしょう。

5.2 三式言語の改造点

5.2.1 いかにクロージャを実現するか?

いかにクロージャを実現しましょうか。

ポイントとして前項より判ったのは、ローカル変数です。 関数が終了してもローカル変数の実体は残っていなければなりません。 しかし、グローバル変数ではなく、ローカル変数のスコープを持ち関数の外部からは直接見えないようにしなければなりません。

さて、どうしましょう。

アセンブラ・レベルまでコンパイルする場合には、関数が最初に呼ばれた場合にヒープから動的メモリ獲得をしてとか考えなければなりません。 でも、今回はC言語へのコンパイルを行います。

じつはC言語には便利な文法があります。

それは、ローカル変数の定義にstaticを付けることです。 C言語では、関数内のローカル変数の定義にstaticを付けると関数が終了しても変数の値は残ったままになります。


int f()
{
   int n;

 ...

関数内のローカル変数の定義にstaticが付かない場合は、その変数は関数呼び出しじにスタック上に確保され、関数終了後は消滅します。

しかし、C言語の文法では、ローカル変数にstaticを付けるとその変数の領域はヒープ上に確保され関数が終了しても残ったままになります。


int f()
{
   static int n;

 ...

これだけです。C言語は便利ですね。

今回はこの機能を使ってクロージャを実現しましょう。

5.2.2 三式言語の改造部分

では、クロージャをサポートした言語に一式言語を改造してみましょう。

まず、変数定義varで、C言語の変数定義を出力する部分にstatic をつけます。


                ]
                {<Comment>}
                [ "var"         <emsg "variable name."> 
                                <printf "static int ">  // ← ここにstaticを追加
                  <ident>       <emsg "variable definition.">
                  {","          <printf ", ">
                   <ident>      <emsg "variable definition.">


さらに関数内でもローカル変数を定義できるようにします。そのために、statementの定義にvar変数の定義処理を新規で追加します。


               | "var"         <emsg "variable name.">
                               <printf "static int ">
                 <ident>       <emsg "variable definition.">
                 {","          <printf ", ">
                  <ident>      <emsg "variable definition.">
                  } ";"        <print ";">

もちろん変数定義出力には、staticを付けています。

これだけの改造で三式言語はクロージャの能力を獲得しました。

5.3 三式言語のソース


// Enhancing PL/0 is remodeled to the language with Crosure.

<program>
                                <setVar Line 1>
                                <print "#include <stdio.h>">
                                <print "#include <stdlib.h>">
                                <print>
                                <print "int main() ">
                                <print "{">
                {<Comment>}
                <block>
                                <print "}">
                {<Comment>}
                ;
<block>         [ "const"       <emsg "constant name.">
                                <printf "const int ">
                  <ident>       <emsg "constant definition.">
                  "="           <printf " = ">
                  <number>
                  {","          <printf ", ">
                   <ident>      <emsg "constant definition.">
                   "="          <printf " = ">
                   <number>
                   }            <emsg "';' is missing.">
                   ";"          <print ";">
                ]
                {<Comment>}
                [ "var"         <emsg "variable name.">
                                <printf "static int ">
                  <ident>       <emsg "variable definition.">
                  {","          <printf ", ">
                   <ident>      <emsg "variable definition.">
                   } ";"        <print ";">
                ]
                {<Comment>}
                { "function"    <emsg "function name.">
                                <printf "int ">
                  <ident>       <emsg "function definition.">
                  "("           <printf "(">
                  [<ident #id1> <printf "int " #id1>
                   { ","        <printf ", ">
                     <ident #id2> <printf "int " #id2>
                   }
                  ]
                  ")"           <print ")">
                  ";"
                                <print "{">
                  <block>       <print "}">
                }
                {<Comment>}
                <statement>
                ;
<statement>     <SKIPSPACE> ::sys <line #Line> <setVar Line #Line>
                {<Comment>}
                (  <ident #var> "="     <emsg "expression.">
                                <printf #var " = ">
                  <expression>  <print ";">
                  ";"           <emsg "syntax error.">
                |
                  <ident #func> <emsg "syntax error.">
                                "(" <emsg "function call.">
                                <printf #func "(">
                        [<expression> {"," <expression>}]
                  ")"           <printf ")">
                                <print ";">
                  ";"           <emsg "syntax error.">
                | "return"      <printf "return ">
                   <expression>
                                <print ";">
                  ";"           <emsg "syntax error.">
                | "var"         <emsg "variable name.">
                                <printf "static int ">
                  <ident>       <emsg "variable definition.">
                  {","          <printf ", ">
                   <ident>      <emsg "variable definition.">
                   } ";"        <print ";">
                | "begin"       <print "{">
                     { <statement> }
                  "end"         <print "}">
                | "if"          <emsg "if sentence.">
                   <printf "if (">
                   <condition>
                   "then"       <print ") {">
                   <statement>
                  ["else"       <print "} else {">
                   <statement>
                  ]
                                <print "}">
                | "while"       <emsg "while sentence.">
                                <printf "while (">
                  <condition>
                  "do"          <print ") {">
                  <statement>
                                <print "}">
                | "for"         <emsg "for sentence.">
                                <printf "for (">
                   <ident>
                     "="        <printf " = ">
                     <expression> ";" <printf ";">
                   <condition> ";" <printf ";">
                   <ident>
                     "="        <printf " = ">
                     <expression> "do" <printf ") {">
                   <statement>
                                <print "}">
                |
                  "print"       <emsg "print sentence. ">
                   [
                    (
                      <strings #str>    <printf 'printf("%s ", "'#str'" );'><print>
                    |                   <printf 'printf("%d ", '>
                      <expression>      <print ');'>
                    )
                   { ","
                    (
                      <strings #str2>   <printf 'printf("%s ", "'#str2'" );'><print>
                    |                   <printf 'printf("%d ", '>
                      <expression>      <print ');'>
                    )
                   }
                   ]
                                        <print 'printf("\n");'>
                  ";"           <emsg "syntax error.">
                )
                ;
<condition>     "odd"           <emsg "odd syntax.">
                                <printf "((">
                  <expression>  <printf ") & 1)">
                |
                <expression>
                ("="            <printf " == ">
                |"#"            <printf " != ">
                |"<="           <printf " <= ">
                |"<"            <printf " < ">
                |">="           <printf " >= ">
                |">"            <printf " > ">
                )
                <expression> ;
<expression>    [ "+"           <printf "+">
                 |"-"           <printf "-">
                ] <term> {
                        ("+"    <printf "+">
                        |"-"    <printf "-">
                        ) <term>};
<term>          <factor> {
                ("*"            <printf "*">
                |"/"            <printf "/">
                ) <factor>};
<factor>        <ident #f>
                 "("            <printf #f "(">
                  [ <expression>
                        {","    <printf ", ">
                        <expression>}]
                 ")"            <printf ")">

                | <ident> | <number>
                | "("           <printf "(">
                        <expression>
                  ")"           <printf ")">
                ;

<ident #id>     <ID #id>
                <reserved #id>
                ;
<ident>         <ident #id>
                <printf #id>
                ;
<number #sign #n>
                ["+"            <is #sign "">
                |
                 "-"            <is #sign "-">
                |
                                <is #sign "">
                ]
                <NUM #n>
                ;
<number>        <number #sign #n>
                <printf #sign #n>
                ;
<strings #str>  <STRINGS #str>
                ;
<strings>       <strings #str>
                <printf #str>
                ;

<Comment>       "//" <SKIPCR>
                ;

<reserved #id>
                <not ::sys <member #id
                  (var begin end if then while for do function
                        return print odd)>>
        ;

<emsg #x>
                <x <getVar #Line Line>
                   <warn "error : " #Line ":" #x>
                   <exit>>
                ;
<emsg2 #x>      <getVar #Line Line>
                <warn "error : " #Line ":" #x>
                <exit>
                ;

<compile>
        ::sys<args #x>
        ::sys<nth #inputfile #x 1>
        ::sys<suffix #outputfile #inputfile c>
        <print inputfile #inputfile>
        <print outputfile #outputfile>
        ::sys<openw #outputfile
                ::sys<openr #inputfile <program>>>
        ;

? <compile>;


5.4 三式言語のソースのコンパイル方法

それでは、三式言語用のサンプルプログラムを作成してコンパイルしてみましょう。

まず、「5.3 三式言語のソース」を pl03.dec と名前をつけて保存しておいてください。

次のサンプルプログラムのソースを使います。


function f();
begin
        var n;

        print n;
        n = n + 1;

end

begin
        f();
        f();
        f();
end

同じ関数f()をメインルーチンで3回呼んでいます。

このサンプルプログラムをa.pl0という名前で保存してコンパイルします。


$ descartes pl03.dec a.pl0
inputfile a.pl0
outputfile a.c
result --
        <compile>
-- true

a.c にコンパイル結果が出力されました。

インデントを直してa.cを見ると次のようになります。


#include <stdio.h>
#include <stdlib.h>

int main()
{
    int f() {
        {
            static int n;
            printf("%d ", n);
            printf("\n");
            n = n + 1;
        }
    }
    {
        f();
        f();
        f();
    }
}

変数 n の定義に、static が付いてますね。

それではgccでコンパイルして実行します。


$ gcc a.c -o a.out

$ ./a.out
0
1
2

想定どおりに結果がインクリメントされて表示されました。