
On Fri, Jun 6, 2008 at 1:29 PM, Giovanni Piero Deretta <gpderetta@gmail.com> wrote:
On Fri, Jun 6, 2008 at 6:16 PM, Daniel Walker <daniel.j.walker@gmail.com> wrote:
Hello,
[...]
However, one thing I noticed right away while reading about Phoenix was that it borrows the term rank-2 polymorphic function from FC++. I'm just learning this terminology, so I'm trying to gain confidence that I understand it. From briefly skimming (McNamara, 2000) in JFP, I'm under the impression that a rank-2 polymorphic function can only have two parametric types, i.e. two template parameters. So, for example, std::make_pair is rank-2 polymorphic, and FC++ has functions that will produce pairs when called with two different types. A rank-n polymorphic function can have arbitrarily many different template parameters; an example would be std::make_tuple. Doesn't Phoenix actually support rank-n polymorphism? For example, isn't the following a rank-3 polymorphic function in Phoenix and couldn't you write similar function objects for higher ranks?
struct tupler_ { template<class> struct result;
template<class F, class T0, class T1, class T2> struct result<F(T0,T1,T2)> { typedef tuple<T0,T1,T2> type; }; template<class T0, class T1, class T2> tuple<T0,T1,T2> operator()(T0 t0, T1 t1, T2 t2) const { return make_tuple(t0,t1,t2); } }; phoenix::function<tupler_> tupler;
For what I understand, rank-n polymorphism has nothing to do with the number of type parameters but It has to do on the level of "higher order nesting" that still allows polymorphic functions to be used polymorphically. If the previous sentence didn't make sense to you (I would be surprised if it did :) ), I'll try with an example (I'll use C++0x notations)
Ah yes. I see now. Thanks! I was reading the bottom of p14 and top of p15 of http://tinyurl.com/6njpbg. I missed that part about the function being called first, and then its result being put into a pair.
<snip>
Rank 2 polymorphism allows a function passed as a parameter to be treated polymorphically:
template<typename F> auto apply_poly(F f) -> decltype(std::make_pair(f(2), std::string("hello world"))) { return std::make_pair(f(2), f(std::string("hello world"))); }
auto x = apply_poly(times_two());
OK, yes. I think this is the important distinction for practical purposes. Monomorphic and rank-1 polymorphic functions are monomorphic at the call site. Rank-n polymorphic functions are polymorphic at the call site. I'm just interested in using these terms for classification purposes, but I don't want to misuse them egregiously with respect to their formal definitions in FP. So, here's how I'd classify various C++ function objects. // monomorphic struct f { int operator()(int); }; // rank-1 polymorphic template<class T> struct g { T operator()(T); }; // rank-n polymorphic struct h { template<class T> T operator()(T); }; That's reasonable, right?
Note that you cannot write the above function using std::function, as it will cause its bound function to be monomorphized (modulo parameter conversion). A multisignature_function would help though.
Just to clarify, for my own understanding as much as anything, if you have a rank-n polymorphic function, you're already good to go. However, if all you have is monomorphic and/or rank-1 polymorphic functions, then multisignature_funciton would be very helpful. This function overload aggregator - call it multisignature_function, overload_function (I'm starting to prefer overload_function to overload_set, now. ;-) ), or whatever - would help to aggregate monomorphic and/or rank-1 polymorphic functions into a single function object that can be used with different argument types, so long as those types are known upfront (i.e. there's not the usual template argument type deduction at the call site - just overload resolution). If you can additionally include rank-n polymorphic functions in the aggregate (and use them polymorphically at the call site with argument type deduction), then that offers more flexibility in the sorts of aggregates you can define.
Rank-3 polymorphism allows passing polymorphic functions as parameters and still threat them as higher order rank-2 polymorphic functions (i.e. they can in turn take a polymorphic function as parameter). You can guess what Rank-4 and more means.
I'm actually having trouble thinking of a practical example of rank-3 usage of a function object. I think it suffices to say the language allows rank-n for templated operator(), though only rank-2 is usual required, at least in the common case.
Modern functional languages (Haskel, ML dialects) allow rank-2 polymorphism (but, I think, not more) at the cost of requiring some limited amount of type annotations.
C++ allows Rank-n polymorphism (i.e. there is no limit on the amount of nesting). The price you pay is no separate compilation (modulo export? but it will probably require some amount of link time compilation), type checking delayed at instantiation time, and of course the amount of type inference that can be done by templates is limited. And yes, both boost.MPL, FC++ and Phoenix actually support rank-n polymorphism.
Now, my understanding of all this stuff is spotty at best, so there is some FP expert is around here, it will very likely correct my explanation
No, this was all very good. I think it helps to get us all on the same page. We don't need to wade too deeply into the minutia of FP language design and whatnot; I'm not interested in drowning! ... only fishing out the good bits. ;-) Daniel Walker