匿名プロシージャ

匿名プロシージャは、Curl® 言語の強力な拡張機能です。この章では、匿名プロシージャについて説明します。具体的には、次の項目について説明します。

匿名プロシージャ入門

要約:
  • 匿名プロシージャには名前がありません。
  • オプションで引数を取ることができます。
  • ゼロ、1 またはそれ以上の値を返すことができます。
  • 任意のコード ブロックまたは式内で宣言できます。
  • 匿名プロシージャを含むコード ブロック内の変数にアクセスできます。
このセクションでは匿名プロシージャについて説明します。Curl 言語の他の種類の関数と同様に、匿名プロシージャにはタスクを実行する実行可能コード ブロックが含まれ、さまざまなデータ型の引数を受け取り、値を返すことができます。
一見では明らかではありませんが、グローバル プロシージャやクラス プロシージャ、さらに匿名プロシージャも含めて、すべての Curl 言語のプロシージャは独自のデータ型を持つファーストクラス オブジェクトです。したがって、次のような状況でプロシージャを使用できます。
匿名プロシージャは上記のコンテキストで使用されるのが最も一般的です。
匿名プロシージャが他のプロシージャと異なる点は次のとおりです。
匿名プロシージャは、グローバル プロシージャやクラス プロシージャと違って、自身の名前を持ちません。ただし、これは匿名プロシージャを呼び出すことができないというわけではありません。匿名プロシージャを変数に代入し、その変数の名前を使って匿名プロシージャを呼び出すことができます。
他の種類の Curl 言語プロシージャと異なり、匿名プロシージャは任意のコード ブロックや式の中で宣言できます。これは、匿名プロシージャを他のプロシージャやメソッドの本体で使用できることを意味します。また、必要に応じてトップレベルの Curl 言語のソース コードで匿名プロシージャを使用することもできます。
他の関数は、そのスコープ内の変数にしかアクセスできません。つまり、グローバル変数、パラメータとしてその関数に渡された変数、およびローカル変数にしかアクセスできません。匿名プロシージャの最も強力な特徴の 1 つは、匿名プロシージャ定義を含むコード ブロックのスコープ内で定義されている変数にアクセスできることです(この特徴は、レキシカル変数の閉鎖サポートとも呼ばれます)。
Curl 言語はさまざまな場所で匿名プロシージャの力を活用しています。ただし、匿名プロシージャはあまり普及していないため、これらの実装の詳細については可能な範囲で非公開にされています。その顕著な例の 1 つが、Curl 言語のイベントの処理です。Curl 言語でイベントを処理する場合、on 式を使用できます。実際のところ、on 式はイベントを処理する匿名プロシージャを作成しているだけです。

匿名プロシージャの作成

匿名プロシージャを作成するには、proc プリミティブを使用します。匿名プロシージャを宣言するのにキーワードが必要なことから、匿名プロシージャは proc とも呼ばれることがあります。匿名プロシージャを作成するには次の構文を使用します。
構文:{proc {[arg-def-list]}[:return-type]
code-body
}
説明:
arg-def-list匿名プロシージャに対する引数リストです。arg-def-list の構文と意味は、Curl 言語のすべての関数と同じです。匿名プロシージャは次の引数カテゴリ、リストされた引数と残余引数の両方をサポートします。さらに次の引数フォーマット、位置引数とキーワード引数の両方をサポートします。引数の構文や意味などの詳細は、「プロシージャ:プロシージャ引数の指定」を参照してください。
return-type匿名プロシージャが返す値のデータ型を指定します。return-type の構文と意味は、Curl 言語のすべての関数と同一です。匿名プロシージャは、1 つまたは複数の戻り値をサポートしています。匿名プロシージャは、説明を目的とする戻り値の名前の使用もサポートしています。戻り値の型指定子の構文や意味などの詳細については、「プロシージャ:プロシージャの戻り値のデータ型の指定」のセクションを参照してください
code-body匿名プロシージャの本体です。code-body はコード ブロックです。1 つ以上の Curl 言語式で構成されます。オプションで return 式が含まれることもあります。匿名プロシージャで 1 つ以上の値を返すように定義するには、return 式を指定します。return 式が存在する場合、return 式の値が匿名プロシージャの呼び出し元に返されます。
引数リストと戻り値のデータ型の指定はオプションであることに注意してください。匿名プロシージャが整数の引数を取り整数値を返す例を次に示します。ここでは引数の値を 3 倍して戻り値にしています。
|| An anonymous procedure that takes one int (x) as
|| an argument and returns an int.
{proc {x:int}:int
    || Return 3 multiplied by the value of the argument.
    {return 3 * x}
}
複数の値を返す匿名プロシージャの例を次に示します。
|| An anonymous procedure that takes one int (x) as
|| an argument and returns two int values.
{proc {x:int}:(int, int)
    || Return two values:
    ||  1) 2 multiplied by the value of the argument
    ||  2) 3 multiplied by the value of the argument
    {return 2 * x, 3 * x}
}

プロシージャのデータ型

匿名プロシージャを変数に代入する場合、変数を宣言する必要があります。変数を宣言するには、プロシージャのデータ型を指定する必要があります。プロシージャのデータ型を設定するには proc-type プリミティブを使います。proc-type プリミティブの構文は次のとおりです。
構文:{proc-type {[arg-type-list]}[:return-type-list]}
説明:
arg-type-listプロシージャに対する引数のデータ型のリストです。データ型を区切るにはカンマを使用します。
return-type-listプロシージャが返す値のデータ型のリストです。値を 1 つ返すプロシージャの場合は、その値のデータ型を指定します。値を複数返すプロシージャの場合は、次の構文を使用します。
(arg-type-list)
arg-type-list はデータ型のリストです。データ型を区切るにはカンマを使います。複数の値を返すプロシージャの場合、Curl 言語では arg-type-list の識別子を追加できます。これらの識別子は、各戻り値の内容を分かりやすく示すことだけを目的として使用されます。その結果、コードを読む人に対する説明の役割を果たします。コードのコンパイル時にはこれらの識別子は破棄されます。
たとえば、次のコードは匿名プロシージャを指す変数を宣言し、初期化しています。

例: 匿名プロシージャを指す変数の宣言と初期化
{value
    || Declare the variable my-proc to be a procedure
    || that takes one int argument and returns an int.
    let my-proc:#{proc-type {int}:int}
    || Initialize the variable.
    set my-proc = 
        {proc {x:int}:int
            {return 3 * x}
        }

    || Call my-proc supplying 2 as a parameter.
    {my-proc 2}
}
次のように変数の宣言と初期化を組み合わせることもできます。

例: 変数の宣言と初期化の組み合わせ
{value
    || Declare the variable my-proc to be a procedure
    || that takes one int argument and returns an int
    || and initialize it with an anonymous procedure.
    let my-proc:{proc-type {int}:int} =
        {proc {x:int}:int
            {return 3 * x}
        }

    || Call my-proc supplying 2 as a parameter.
    {my-proc 2}
}
次のコードは複数の値を返す匿名プロシージャを宣言し、初期化します。

例: 複数の値を返す匿名プロシージャの宣言と初期化
{value
    || Declare the variable my-proc to be a procedure
    || that takes one int argument and returns two
    || int values.
    let multiply:{proc-type {int}:(int, int)} =
        {proc {x:int}:(double:int, triple:int)
            {return 2 * x, 3 * x}
        }

    || Declare two variables a and b to be
    || variables with the any data type (default).
    || Initialize the variables with the return value
    || of a call to multiply for the value 3.
    let (a, b) = {multiply 3}

    || Display a and b.
    {format "a = %d, b = %d", a, b}
}
この例では戻り値のデータ型リストに識別子が追加されていることに注意してください。これらの識別子の目的は、それぞれの戻り値の内容を示すことだけです。

引数として渡す匿名プロシージャ

要約:
  • 関数呼び出しの引数として匿名プロシージャを使用できます。
匿名プロシージャはデータ型を持つため、これを関数呼び出しの引数として使用できます。Curl 言語に組み込まれているコレクション クラスにはこれを利用して、メソッド呼び出しのパラメータとして評価対象のプロシージャを指定できるものがいくつかあります。たとえば Aggregate-of.filter メソッドでは、コレクションの要素をフィルタする際にプロシージャを指定することができます。Aggregate-of.filter メソッドの使用例を次に示します。

例: 引数として渡す匿名プロシージャ
{value
    || A set of Strings that contains three elements.
    let fruits:{Set-of String} =
        {new {Set-of String}, "apple", "banana", "cherry"}

    || Filter out elements that begin with 'a'.  Note that
    || the filter method removes an element if a call to
    || the supplied anonymous procedure returns false.
    {fruits.filter {proc {str:String}:bool
                       {return not str.empty? and str[0] != 'a'}
                   }
    }

    || Display the elements that remain in the set.
    || One way to display the members of a set is to
    || create a StringBuf and concatenate the elements
    || of the set to the StringBuf.  We then return
    || the value of the StringBuf.
    let display:StringBuf = {StringBuf ""}
    {for s:String in fruits do
        || Concatenate the element to the StringBuf.
        {display.concat s}
        || Append a space to the StringBuf (to separate the
        || elements in the display).
        {display.append ' '}
    }
    display
}
上の例では、通常のプロシージャを定義して、Aggregate-of.filter を呼び出す際の引数としてこのプロシージャ名を渡すこともできます。だたし、匿名プロシージャを使用することでコード内の識別子の数やコードの行数を削減しています。これらの理由だけでは匿名プロシージャの利点を十分に理解していただけないかもしれません。以下のセクションでは、非常に強力な機能を紹介して、匿名プロシージャの本当の能力について説明します。

匿名プロシージャとスコーピング

要約:
  • 匿名プロシージャを含むコード ブロック内にある変数にアクセスできます。
匿名プロシージャのスコープは、それを含むコード ブロックにまで及びます。つまり、匿名プロシージャ内の式は匿名プロシージャの外で定義された変数にもアクセスできるということです。ただし、匿名プロシージャを含むコード ブロックのスコープ内で定義された変数に限られます。
次の例では、かなり込み入った方法で引数を 3 倍した値を返すプロシージャを示しています。この例の目的は、匿名プロシージャを含むコード ブロックからアクセス可能な変数に、どのようにして匿名プロシージャがアクセスできるかを示すことです。

例: 匿名プロシージャとスコーピング
|| A procedure that takes one int argument (x) and returns
|| an int.
{define-proc {triple x:int}:int
    || Define an anonymous procedure that takes one int
    || argument and returns an int.
    let my-proc:{proc-type {int}:int} =
        {proc {b:int}:int
            || Return the value of the variable x multiplied
            || by the value of the argument (b).  The
            || anonymous procedure accesses variables in the
            || scope of the code block containing the
            || anonymous procedure.
            {return x * b}
        }
    || The body of the main procedure... return the value
    || of a call to my-proc with the argument 3.
    {return {my-proc 3}}
}

{value
    || Call the procedure triple with the value 9 as the
    || argument.
    {triple 9}
}

戻り値としての匿名プロシージャ

要約:
  • 匿名プロシージャを関数呼び出しの戻り値として使用できます。
匿名プロシージャは、関数呼び出しの戻り値としても使用できます。次の例では、adder プロシージャが匿名プロシージャを返します。このような匿名プロシージャを呼び出すためには、戻り値を変数に代入し、通常のプロシージャ名と同じようにその変数名を、匿名プロシージャを呼び出すのに使用します。次に例を示します。

例: 戻り値としての匿名プロシージャの使用
|| A procedure that takes one int argument (x) and returns
|| a procedure.
{define-proc {adder x:int}:{proc-type {int}:int}
    || Return an anonymous procedure.
    {return
        {proc {y:int}:int
            || The anonymous procedure returns the sum of
            || the argument to "adder" that originally
            || creates the anonymous procedure (x) and the
            || argument to the anonymous procedure call (y).
            {return x + y}
        }
    }
}

{value
    || Define a variable whose data type is a procedure.
    || Initialize the variable with the return value from a
    || call to adder with an argument of 3.
    let add3:{proc-type {int}:int} = {adder 3}
    || This essentially creates a variable that is a handle
    || for a procedure that adds 3 to the value of a supplied
    || argument.

    || Call the variable with the argument 5.
    {add3 5}
}

これまでは、レキシカル変数の閉鎖、引数として渡す匿名プロシージャ、および戻り値としての匿名プロシージャを紹介しました。次の 2 つの例ではさらにこれらの概念を補足します。

例: 匿名プロシージャの高度な使用 (パート 1)
|| A procedure that takes one argument (an int) and returns
|| two values (both any).  The two return values are, in
|| fact, anonymous procedures.
{define-proc {new-counter val:int}:(any, any)
    {return
        || An anonymous procedure that returns the original
        || argument.
        {proc {}:int {return val}},
        || An anonymous procedure that sets the value of the
        || original argument.
        {proc {new:int}:void {set val = new}}}}

|| Define two variables my-getter and my-setter to be anys.
|| Initialize the two variables with the return values of
|| a call to new-counter with 2 as the argument.
{let (my-getter:any, my-setter:any) = {new-counter 2}}
|| Define two variables your-getter and your-setter to be anys.
|| Initialize the two variables with the return values of
|| a call to new-counter with 3 as the argument.
{let (your-getter:any, your-setter:any) = {new-counter 3}}

|| A call to my-getter should return the original
|| argument (2).
{my-getter}

|| A call to your-getter should return the original
|| argument (3).
{your-getter}

|| A call to my-setter should change the original argument.
{my-setter 7}
|| And now a call to my-getter should return the updated
|| argument.
{my-getter}

|| A call to your-getter should still return the original
|| argument (3).
{your-getter}

|| A call to your-setter should change the original argument.
{your-setter -1}
|| And now a call to your-getter should return the updated
|| argument.
{your-getter}

|| And finally, a call to my-getter should return 7.
{my-getter}

例: 匿名プロシージャの高度な使用 (パート 2)
|| A procedure that takes a procedure argument and returns a
|| procedure.
{define-proc {twice f:{proc-type {int}:int}}:{proc-type {int}:int}
    || Returns an anonymous procedure.
    || The anonymous procedure calls the procedure that is
    || supplied as an argument two times.  First, it calls the
    || procedure with the value of the integer parameter to the
    || anonymous procedure.  Then, it calls the procedure with
    || the result of the previous call.
    {return
        {proc {x:int}:int
            {return {f {f x}}}
        }
    }
}

{value
    || Define a variable whose data type is a procedure.
    || Initialize the variable with an anonymous procedure.
    || The anonymous procedure takes one integer argument and
    || returns an integer value that is double the value of
    || the argument.
    let double-proc:{proc-type {int}:int} =
        {proc {y:int}:int
            {return 2 * y}
        }

    || Define a variable whose data type is a procedure.
    || Initialize the variable with a call to the procedure
    || "twice" with the procedure "double-proc" as an argument to
    || the call.
    let quadruple-proc:{proc-type {int}:int} = {twice double-proc}

    {quadruple-proc 3}
}

匿名プロシージャでの any データ型の使用

要約:
  • コードを読みやすくするために、匿名プロシージャの代わりに any データ型を使用できます。
匿名プロシージャの構文は、結果的に読みにくいコードになります。読みやすいコードを記述するために、匿名プロシージャ宣言を指定する代わりに any データ型を使用できます。ただし、any データ型を使用すると実行時の速度に影響を及ぼします。これを理解した上で、前述の例を次のように書き直すことができます。

例: 匿名プロシージャでの any データ型の使用
|| A procedure that takes a procedure and returns a new
|| procedure.
{define-proc {twice f}
    || Returns an anonymous procedure.
    || The anonymous procedure calls the procedure that is
    || supplied as an argument two times.  First, it calls the
    || procedure with the value of the integer parameter to the
    || anonymous procedure.  Then, it calls the procedure with
    || the result of the previous call.
    {return {proc {x:int}:int
                {return {f {f x}}}
            }
    }
}

{value
    || Define a variable and initialize it with an anonymous
    || procedure.  The anonymous procedure takes one integer
    || argument and returns an integer value that is double
    || the value of the argument.
    let double-proc = {proc {y:int}:int
                          {return 2 * y}
                      }

    || Define a variable and initialize it with a call to the
    || procedure "twice" with the procedure "double-proc" as an
    || argument to the call.
    let quadruple-proc = {twice double-proc}

    {quadruple-proc 3}
}