Boost logo

Boost :

From: Daniel Walker (daniel.j.walker_at_[hidden])
Date: 2008-06-06 15:48:15


On Fri, Jun 6, 2008 at 1:29 PM, Giovanni Piero Deretta
<gpderetta_at_[hidden]> wrote:
> On Fri, Jun 6, 2008 at 6:16 PM, Daniel Walker <daniel.j.walker_at_[hidden]> 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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk