| 関数 |
![]() |
![]() |
![]() |
より詳細に見ていけば、 Spirit が全てパーサ関数のコンポジションであることに気づくだろう。 パーサは、スキャナを受け入れ、マッチを返す、ただの関数である。 パーサ関数は合成され、徐々に複雑な高階形式を形成する。 オブジェクトに働くパーサは不変(immutable)かつ一定(constant)であることにも注意すること。 すべてのプリミティブおよびコンポジットパーサオブジェクトはconstである。 parse メンバ関数すらもconstで宣言されている:
template <typename ScannerT>
typename parser_result<self_t, ScannerT>::type
parse(ScannerT const& scan) const;
あらゆる点で、これは関数プログラミングの見た目や雰囲気と非常によく似ている。そして実際、その通りだ。 あらゆる意味で、 Spirit は手続き型な C++ ドメインにおける関数プログラミングを応用したものである。 Haskell では、例えば、パーサコンビネータと呼ばれるものがある、 これは Spirit が取ったのと非常に類似したアプローチである - すなわち、トップダウン型再帰下降型パーサをモデル化するより高階なパーサ関数を、様々な演算子で合成されたパーサ関数で作成するのである。 賢明な Haskell の人々はこの方法を Spirit より先に行っていた。
関数スタイルプログラミング(あるいは FP )ライブラリは C++ コミュニティに受け入れられつつある。 確実に、我々はSpiritの現在と未来においてより進んだFPと出会うだろう。 実際、より詳しく見ていけば、 C++ 標準ライブラリでさえ FP の風味を持っていることが分かる。 STL をより詳細に検証すれば、 標準 C++ ライブラリのコアの下には、密かに真の FP パラダイムが既に存在している様子を垣間見ることができる。 STL の著者たちが FP の存在を知っており、実践していることは明らかだ。
Spirit での STL-スタイルの FP の、より分かりやすい応用はセマンティックアクションであある。 STL-スタイル の FP とは? 第一に、高階ファンクタの形に合成可能なファンクタを用いることである。
| 関数オブジェクトあるいはファンクタとは、単にあたかも関数のように呼び出せるあらゆるオブジェクトを指す。通常の関数は関数オブジェクトの一つである。関数ポインタも同様;より一般的には、 operator() が定義されたクラスのオブジェクトのことを指す。 |
このSTL-スタイル FP は今日至る所で目にすることが出来る。 次の例はSGI's Standard Template Library Programmer's Guideから採ったものである:
// 範囲内の各要素に対して sin(x)/(x + DBL_MIN) を計算する。
transform(first, last, first,
compose2(divides<double>(),
ptr_fun(sin),
bind2nd(plus<double>(), DBL_MIN)));
そう、これはまさに FP 用語でいう カリー化である。
| "カリー化" とは何か?どこから来たのか? カリー化はその起源を関数の数学的研究に持っている。 関数を単一引数に制限するという興味を満たすものとして、フレーゲ(Frege)によって1893年に発見された。 例えば、任意の二引数の関数 f(x,y) について、一引数の関数 f' が存在する。例えば f'(x) は、 (f'(x))(y) = f (x,y)を与えるために y を適用されることができる関数である。 これは (AxB -> C) および (A -> (B -> C))が同型であるというよく知られた事実と一致している。ここで "x"はデカルト積であり、"->"は関数空間を指す。 関数プログラミングでは、関数の適用は並置によって記述され、左へ関連づけられると仮定される。そのため上述の等式は f' x y = f(x,y) となる。 |
Spirit の文脈では、 同じ FP スタイルのファンクタのコンポジションがセマンティックアクションに適用されることがある。その良い例が full_calc.cpp で、これは libs/example/fundamental/calc フォルダにある。以下はその例の断片である:
expression =
term
>> *( ('+' >> term)[make_op(plus<long>(), self.eval)]
| ('-' >> term)[make_op(minus<long>(), self.eval)]
)
;
Boost はさらに FP パラダイムを取り入れている。 boost には、関数オブジェクトや高階プログラミングに特に焦点を当てたライブラリが存在する。
| Boost FP ライブラリ | |||||||||||||
| bind と mem_fn | 関数/オブジェクト/ポインタおよびメンバ関数のための、一般化された束縛子(binder)、 Peter Dimov より | compose | STL用の関数コンポジションアダプタ、 Nicolai Josuttis より | function | 遅延呼び出しやコールバックのための関数オブジェクトラッパー(wrapper)、 Doug Gregor より | functional | 拡張された関数オブジェクトアダプタ、 Mark Rodgers より | lambda | 関数呼び出し側に小さな無名関数オブジェクトを定義する、など、 Jaako Järvi と Gary Powell より | ref | ジェネリックな関数に参照を渡すためのユーティリティライブラリ、Jaako Järvi 、 Peter Dimov 、 Doug Gregor および Dave Abrahams より | ||
以下はメンバ関数を Spirit のセマンティックアクションとして使うために boost の Bind を用いる例である。 libs/example/fundamental/ ディレクトリにある spirit_bind.cpp ファイルでこの例の全てを見ることができる。
class list_parser
{
public:
typedef list_parser self_t;
bool
parse(char const* str)
{
return spirit::parse(str,
// 文法の始まり
(
real_p
[
bind(&self_t::add, this, _1)
]
>> *( ','
>> real_p
[
bind(&self_t::add, this, _1)
]
)
)
,
// 文法の終わり
space_p).full;
}
void
add(double n)
{
v.push_back(n);
}
vector<double> v;
};
このパーサはカンマで区切られた実数のリストを構文解析し、 vector<double> に格納する。 Boost.bind は list_parserのメンバ関数 addから Spirit に適合するセマンティックアクションを作成する。
小生が書いた Phoenix という名前のライブラリがある。 これは Spirit 配布物の公式な一部ではないが、このライブラリは C++ におけるさらなる FP 技法の実験のために広く用いられている。 このライブラリはFC++ と boost Lambda (BLL)に大きく影響を受けている。
| Phoenix が boost Lambda (BLL)の影響を受けているのと同じくらい、 Phoenix の革新である局所変数、局所関数それに適応型クロージャなどは順次 BLL に影響した。 今や、 BLL は非常に Phoenix に似ている。 最も重要なこととしては、 BLL は Phoenix の適応型クロージャを併合した。 将来、 Spirit は BLL を完全にサポートするだろう。 |
Phoenix は、 ラムダ(無名関数)式を用いて C++ でインラインにセマンティックアクションを書けるようにする。以下は phoenix_calc.cpp (これも in the libs/example/fundamental/calc フォルダで見つけられる)から切り出した例である:
expression
= term[expression.val = arg1]
>> *( ('+' >> term[expression.val += arg1])
| ('-' >> term[expression.val -= arg1])
)
;
term
= factor[term.val = arg1]
>> *( ('*' >> factor[term.val *= arg1])
| ('/' >> factor[term.val /= arg1])
)
;
factor
= ureal_p[factor.val = arg1]
| '(' >> expression[factor.val = arg1] >> ')'
| ('-' >> factor[factor.val = -arg1])
| ('+' >> factor[factor.val = arg1])
;
ラムダ式の次のような使い方に注目して欲しい:
expression.val += arg1
| 実際にはラムダ式は、 プレースホルダ(例えば arg1、 arg2)が引数のいくつかの代わりに与えられた場所で部分的に適用された無名の関数である。 これがラムダ式と呼ばれる理由は伝統的なもので、 そのようなプレースホルダがギリシャ文字のラムダ |
ここで expression.val は expression ルールのクロージャ変数である(Closuresを参照)。 arg1 はセマンティックアクションが受け取る第一引数のプレースホルダである(Phoenix Place-holdersを参照)。
Boost.Lambda(BLL)では、これは _1 に相当する。
![]() |
![]() |
![]() |
Copyright © 1998-2003 Joel de Guzman
Permission to copy, use, modify, sell and distribute this document
is granted provided this copyright notice appears in all copies. This document
is provided "as is" without express or implied warranty, and with
no claim as to its suitability for any purpose.
Japanese Translation Copyright © 2003 Kent.N
オリジナルの、及びこの著作権表示が全ての複製の中に現れる限り、この文書の複製、利用、変更、販売そして配布を認める。このドキュメントは「あるがまま」に提供されており、いかなる明示的、暗黙的保証も行わない。また、いかなる目的に対しても、その利用が適していることを関知しない。
このドキュメントの対象: Boost Version 1.30.0
最新版ドキュメント(英語)