📄 phoenix_users_manual.txt
字号:
[doc Phoenix v1.2]
[/ QuickDoc Document version 0.5 ]
[/ Images ]
[def __note__ [$theme/note.gif]]
[def __alert__ [$theme/alert.gif]]
[def __detail__ [$theme/lens.gif]]
[def __tip__ [$theme/bulb.gif]]
[def :-) [$theme/smiley.gif]]
[/ Links ]
[def Spirit [@http://spirit.sourceforge.net Spirit]]
[def Haskell [@http://www.haskell.org Haskell]]
[def boost_lambda [@http://www.boost.org/libs/lambda/doc/index.html BLL]]
[def FC++ [@http://www.cc.gatech.edu/~yannis/fc++/ FC++]]
[def spirit_docs [@../../spirit/index.html '''Spirit''' v1.6]]
[def spirit_mlist [@https://lists.sourceforge.net/lists/listinfo/spirit-general '''Spirit''' Mailing List]]
[def Boost [@http://www.boost.org Boost]]
[page Preface]
[:['Functional programming is so called because a program consists entirely of functions. The main program itself is written as a function which receives the program's input as its argument and delivers the program's output as its result. Typically the main function is defined in terms of other functions, which in turn are defined in terms of still more functions until at the bottom level the functions are language primitives.]]
[:[*['John Hughes]]-- ['Why Functional Programming Matters]]
[h2 Influences and Related Work]
The design and implementation of Phoenix is highly influenced by FC++ by Yannis Smaragdakis and Brian McNamara and the (Boost Lambda Library) boost_lambda by Jaakko J鋜vi and Gary Powell. Phoenix is a blend of FC++ and boost_lambda using the implementation techniques used in the Spirit inline parser.
Is Phoenix better than FC++ or boost_lambda? Well, there are concepts found in Phoenix that are not found in either library. FC++ has rank-2 polymorphic functions (FC++ jargon) which Phoenix also has, boost_lambda has syntactic sugared operators which FC++ lack, that Phoenix also has.
Phoenix inherits FC++'s rank-2 polymorphic functions. Rank-2 polymorphic functions are higher order functions that can accept polymorphic arguments. FC++ is the first library to enable higher order polymorphic functions. Before FC++, polymorphic functions couldn't be used as arguments to other functions.
What really motivated the author to write Phoenix is the lack of access to a true stack-frame with local variables (closures) in all C++ FP libraries in existence so far. When emulating functions in the form of functors, the most basic ingredient is missing: local variables and a stack. Current FP libraries emulate closures using state variables in functors. In more evolved FP applications, this "poor man's closure" is simply inadequate.
Perhaps boost_lambda does not need this at all since unnamed lambda functions cannot call itself anyway; at least not directly. FC++ arguably does not need this since it is purely functional without side-effects, thus there is no need to destructively write to a variable. The highly recursive nature of the Spirit framework from which Phoenix is a derivative work necessitated true reentrant closures. Later on, Phoenix will inherit the Spirit framework's true closures which implement access to true hardware stack based local variables.
Phoenix is also extremely modular by design. One can extract and use only a small subset of the full framework, literally tearing the framework into small pieces, without fear that the pieces won't work anymore. For instance, one can use only the FC++ style programming layer with rank-2 polymorphic functions without the sugared operators.
Emphasis is given to make Phoenix much more portable to current generation C++ compilers such as Borland and MSVC. Borland for example chokes on both boost_lambda and FC++ code. Forget MSVC support in FC++ and boost_lambda. On the other hand, although Phoenix is not yet ported to MSVC, Phoenix uses the same tried and true implementation techniques used by the Spirit framework. Since Spirit has been ported to MSVC by Bruce Florman (v1.1) and by Raghav Satish (v1.3), it is very likely that Phoenix will also be ported to MSVC.
Finally, and most importantly though, Phoenix is intended, hopefully, to be much more easier to use. The focus of Phoenix (and Spirit for that matter), is the typical practicing programmer in the field rather than the gurus and high priests. Think of Phoenix as the C++ FP library for the rest of us :-)
[h2 How to use this manual]
The Phoenix framework is organized in logical modules. This documentation provides a user's guide and reference for each module in the framework. A simple and clear code example is worth a hundred lines of documentation; therefore, the user's guide is presented with abundant examples annotated and explained in step-wise manner. The user's guide is based on examples. Lots of them.
As much as possible, forward information (i.e. citing a specific piece of information that has not yet been discussed) is avoided in the user's manual portion of each module. In many cases, though, it is unavoidable that advanced but related topics not be interspersed with the normal flow of discussion. To alleviate this problem, topics categorized as "advanced" may be skipped at first reading.
Some icons are used to mark certain topics indicative of their relevance. These icons precede some text to indicate:
[table Icons
[__note__] [[*Note]] [Information provided is moderately important and should be noted by the reader.]
[__alert__] [[*Alert]] [Information provided is of utmost importance.]
[__detail__][[*Detail]] [Information provided is auxiliary but will give the reader a deeper insight into a specific topic. May be skipped.]
[__tip__] [[*Tip]] [ A potentially useful and helpful piece of information.]
]
This documentation is automatically generated by Spirit QuickDoc documentation tool. QuickDoc is part of the Spirit distribution [ See libs/spirit/example/application/quickdoc/ ].
[h2 Support]
Please direct all questions to Spirit's mailing list. You can subscribe to the spirit_mlist. The mailing list has a searchable archive. A search link to this archive is provided in Spirit's home page. You may also read and post messages to the mailing list through an [@news://news.gmane.org/gmane.comp.spirit.general NNTP news portal] (thanks to www.gmane.org). The news group mirrors the mailing list. Here are two links to the archives: via [@http://news.gmane.org/thread.php?group=gmane.comp.spirit.general gmane], via [@http://www.geocrawler.com/lists/3/SourceForge/12837/0/ geocrawler].
[*['To my dear daughter Phoenix]][br][*Joel de Guzman][br]September 2002[br]
[page Introduction]
[h1 The Phoenix Framework v1.2]
[h4 Preliminary Draft]
February 2001, Joel de Guzman
Functional programming (or FP) is gaining momentum as more programmers discover its power. In its purest form, the paradigms set forth seem to be too detached from what most programmers are already accustomed to. In the point of view of the C or Pascal imperative programmer, for instance, FP techniques and concepts are quite esoteric at first glance. Learning a pure FP language such as Haskell for example requires a significant quantum leap.
FP can be taken as a methodology that is not at all tied to a specific language. FP as a programming discipline can be applied to many programming languages. In the realm of C++ for instance, we are seeing more FP techniques being applied. C++ is sufficiently rich to support at least some of the most important facets of FP such as higher order functions. C++ deservedly regards itself as a multiparadigm programming language. It is not only procedural; it is not only object oriented; stealthily beneath the core of the standard C++ library, a closer look into STL gives us a glimpse of a truly FP paradigm already in place. It is obvious that the authors of STL knows and practices FP. In the near future, we shall see more FP trickle down into the mainstream. Surely.
The truth is, although FP is rich in concepts new and alien to the typical C++ programmer, we need not shift the paradigm in its entirety wholesale; but rather in small pieces at a time. In fact, most of the FP techniques can coexist quite well with the standard object oriented and imperative programming paradigms. When we are using STL algorithms and functors for example, we are already doing FP.
Phoenix extends the concepts of FP to C++ much further. In a nutshell, the framework opens up FP techniques such as Lambda (unnamed functions) and Currying (partial function evaluation).
[page Quick start]
To get a first glimpse on what the Phoenix framework offers, let us start with an example. We want to find the first odd number in an STL container.
1) Normally we use a functor or a function pointer and pass that in to STL's find_if generic function (sample1.cpp):
Write a function:
bool
is_odd(int arg1)
{
return arg1 % 2 == 1;
}
Pass a pointer to the function to STL's find_if generic function:
find_if(c.begin(), c.end(), &is_odd)
2) Using Phoenix, the same can be achieved directly with a one- liner (sample2.cpp):
find_if(c.begin(), c.end(), arg1 % 2 == 1)
The expression "arg1 % 2 == 1" automagically creates a functor with the expected behavior. In FP, this unnamed function is called a lambda function. Unlike 1, the function pointer version, which is monomorphic (expects and works only with a fixed type int argument), the Phoenix version is completely polymorphic and works with any container (of ints, of doubles, of complex, etc.) as long as its elements can handle the "arg1 % 2 == 1" expression.
3) Write a polymorphic functor using Phoenix (sample3.cpp)
struct is_odd_ {
template <typename ArgT>
struct result { typedef bool type; };
template <typename ArgT>
bool operator()(ArgT arg1) const
{ return arg1 % 2 == 1; }
};
function<is_odd_> is_odd;
Call the lazy is_odd function:
find_if(c.begin(), c.end(), is_odd(arg1))
is_odd_ is the actual functor. It has been proxied in function<is_odd_> by is_odd (note no trailing underscore) which makes it a lazy function. is_odd_::operator() is the main function body. is_odd_::result is a type computer that answers the question "What should be our return type given an argument of type ArgT?".
Like 2, and unlike 1, function pointers or plain C++ functors, is_odd is a true lazy, polymorphic functor (rank-2 polymorphic functoid, in FC++ jargon). The Phoenix functor version is fully polymorphic and works with any container (of ints, of doubles, of complex, etc.) as long as its elements can handle the "arg1 % 2 == 1" expression. However, unlike 2, this is more efficient and has less overhead especially when dealing with much more complex functions.
This is just the tip of the iceberg. There are more nifty things you can do with the framework. There are quite interesting concepts such as rank-2 polymorphic lazy functions, lazy statements, binders etc; enough to whet the appetite of anyone wishing to squeeze more power from C++.
[page Basic Concepts]
Everything is a function (class actor) in the Phoenix framework that can be evaluated as f(a..n), where n is the function's arity, or number of arguments that the function expects. Operators are also functions. For example, a + b is just a function with arity == 2 (or binary). a + b is the same as plus(a, b), a + b + c is the same as plus(a, plus(b, c)). plus(a, plus(b, c)) is a ternary function (arity == 3).
Amusingly, even functions return functions. We shall see what this means in a short while.
Currying, named after the famous Logician Haskell Curry, is one of the important mechanisms in the programming discipline known as functional programming (or FP). There's much theory surrounding the concepts behind it, however, in the most simplest term, it is safe to assume that "currying" a function is more or less like partially evaluating a function. Take a simple pseudo C++ function:
plus(x, y) { return x + y; }
for example. Fully evaluating the function 'plus' above is done by supplying the arguments for x and y. For example:
plus(3, 2)
will give us 5. On the other hand, partial evaluation can be thought of as calling the function without supplying all the arguments. Here's an imaginary (non-C++) example:
plus(?, 6)
What does this mean and what is the function's result? First, the question mark proclaims that we don't have this argument yet, let this be supplied later. We have the second argument though, which is 6. Now, while the fully evaluated function plus(3, 2) results to the actual computed value 5, the partially evaluated function plus(?, 6) results to another (unnamed) function (A higher order function. In FP, the unnamed function is called a lambda function), this time, the lambda function expects one less argument:
plus(3, 2) --> 5
plus(?, 6) --> unnamed_f_x_plus_6(x)
now, we can use unnamed_f_x_plus_6, which is the result of the expression plus(?, 6) just like a function with one argument. Thus:
plus(?, 6)(3) --> 9
This can be understood as:
| plus(?, 6) | (3) |
|_____f1_____| |
|_____f2___________|
* f1 is the result of partially evaluating plus(?, 6)
* f2 is the result of the fully evaluated function passing 3 where f1 has the ? placeholder, thus plus(3, 6)
The same can be done with operators. For instance, the above example is equivalent to:
3 + 2 --> 5
? + 6 --> unnamed_f_x_plus_6(x)
Obviously, partially evaluating the plus function as we see above cannot be done directly in C++ where we are expected to supply all the arguments that a function expects. Simply, currying the function plus is not possible in straight C++. That's where the Phoenix framework comes in. The framework provides the facilities to do partial function evaluation.
[page Architecture]
Care and attention to detail was given, painstakingly, to the design and implementation of Phoenix. The overall design of the framework is well structured and clean. In this chapter, we shall see the main concepts behind the framework and gain introductory insights regarding its design.
[blurb __detail__ [*Macros][br][br]Implementation wise, not a single macro was used. Macros cause more trouble than its worth, regardless if they are used only in the implementation. A very early version of the framework did use macros to generate redundant code. The experience was to say the least, painful. 1) The code is so much more difficult to read 2) Compile errors take you in the middle of nowhere in a meaningless macro invocation without the slightest clue whatsoever what went wrong. The bottom line is: Macros are plain ugly. Exclamation point! No to macros. Period.]
[page:1 Lazy functions]
When currying or partial function evaluation takes place, supplying N actual arguments to a function that expects M arguments where N < M will result in a higher order function with M-N arguments. Technically, when N == M, the function has all the arguments needed to do a full evaluation:
plus(3, 2) full evaluation
plus(?, 6) partial evaluation
[blurb __note__ Lazy functions are subsets of partial function evaluation or currying]
Now, we shall revisit the concept of lazy functions introduced before in passing. That is, the first function invocation will not really "fully evaluate" the function regardless if all or some of the arguments are supplied. A second function invocation will always be needed to perform the actual evaluation. At the point in the second call, the caller is expected to supply all arguments that are still missing. Still vague? To clarify, a partially evaluated function:
f(1, ?, ?)
results to an unnamed function unnamed_f(a, b) that expects the two (2) more arguments that are still missing when the first function, f, is invoked. Since unnamed_f(a, b) is already a second level evaluation, all arguments must be supplied when it is called and the result is finally evaluated. Example:
f(1, ?, ?) ---> unnamed_f(a, b)
then
unnamed_f(2, 3) ---> evaluate_and_return_value_for f(1, 2, 3)
This function call sequence can be concatenated:
f(1, ?, ?)(2, 3)
The second level function unnamed_f is not curryable. All of its still missing arguments must be supplied when it is called.
As mentioned, even though we have all the arguments in the first call, the function is not yet evaluated (thus lazy). For example, a function call:
f(1, 2, 3)
remember that the first function invocation will not really evaluate the function even if all the arguments are fully supplied. Thus:
f(1, 2, 3) ---> unnamed_f()
[blurb __note__ [*Generators][br][br]In FP, unnamed_f() is a generator; a function that has no arguments but returns a result. Not to be confused with Phoenix generators to be discussed later.]
Then:
unnamed_f() ---> evaluate_and_return_value_for f(1, 2, 3)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -