Boost logo

Boost :

Subject: Re: [boost] painless currying
From: Mathias Gaunard (mathias.gaunard_at_[hidden])
Date: 2011-08-24 12:16:12


On 24/08/2011 04:59, Eric Niebler wrote:

>> Sorry, this should have been
>> int i = (lazy(std::plus<int>())(1) + 4)(3)
>> of course.
>
> Could you explain this?

lazy is just

template<class T>
phoenix::function<T> lazy(const T& f)
{
    return phoenix::function<T>(f);
}

lazy(std::plus<int>()) turns std::plus<int>() into a lazy function (I
said phoenix actor before -- I think that's incorrect terminology,
sorry); i.e. the operator() members return an expression instead of
evaluating it.
Of course you could also call this 'make_function' instead of 'lazy'.
In this particular case, phoenix::function< std::plus<int> >() would
work just as well, of course.

Now what I'm suggesting is to add currying in Boost.Phoenix by
implementing the expected logic in the call node evaluation.
This has the desired effect of allowing
lazy(std::plus<int>())(1)(2)

But it also has the interesting effect of also allowing to do
(lazy(std::plus<int>())(1) + 4)(3)
as I wrote above. More about this below.

I think that's pretty interesting since it essentially allows us to write
(f + g)(x) to do f(x) + g(x)
I haven't thought about this enough to tell whether it is really
desirable or not.

Let me unroll the example.

so lazy(std::plus<int>())(1) + 4
is a tree similar to (pseudo-proto with values embedded in the types)
plus<
    call< terminal< std::plus<int> >,
          terminal< int, 1 >
>,
    terminal< int, 4 >
>

Now, when you run (lazy(std::plus<int>())(1) + 4)(3) you evaluate the
above tree with the tuple (3) as the state.

When evaluating a call node, you do the following:
- if enough arguments are passed, evaluate the arguments then call the
function on the evaluated arguments (default transform -- what is
currently being done)
- if not enough arguments are passed, add terminal children to the call
node, which reference the value from the state tuple until the function
has enough arguments. Then evaluate the node as above.

So you end up to something semantically equivalent to evaluating the
following tree with the default transform

plus<
    call< terminal< std::plus<int> >,
          terminal< int, 1 >,
          terminal< int, 3 >
>,
    terminal< int, 4 >
>

i.e., std::plus<int>(1, 3) + 4.

This however, appears to have some possible issues, but nothing really
problematic:

let's consider I want to call

foo(bar(1)) + _1

with foo taking one argument which must be a function, and bar taking
two integers.
both foo and bar are lazy functions.

when I call it with a state of (2), this will "expand" to

foo(bar(1))(2) + _1(2)
foo(bar(1)(2)) + _1(2)
foo(bar(1, 2)) + 2

which is not what I wanted (foo(bar(1)) + 2)

phoenix::lambda[foo(bar(1)] + _1, however, will do what's desired, since
it will mask the arguments to the lambda-body.

Of course, foo(bar(1))() works as expected.

>> Detecting and propagating monomorphism could be nice though. It could
>> eventually provide better error messages, faster compilation times, and
>> automatic currying on monomorphic function objects.
>
> What do monomorphic functions have to do with this? My currying code
> works with polymorphic functions.

I think it would be much safer to restrict it to monomorphic functions
to avoid ambiguities.

But then, I don't have a strong opinion on this.


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