Boost logo

Boost :

Subject: Re: [boost] painless currying
From: Thomas Heller (thom.heller_at_[hidden])
Date: 2011-08-24 12:43:41


On Wed, Aug 24, 2011 at 6:16 PM, Mathias Gaunard
<mathias.gaunard_at_[hidden]> wrote:
> 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);
> }

This can be done, and makes sense. It is, in a way similar to bind
though, that is why i didn't add something like this.

> 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.

Exactly, see phoenix::function as some kind of a expression generator.
To get the terminology right, the call to a operator() returns an
expression, which is a phoenix actor.

> 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.

Right, i like lazy though.

> 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.

Nice! I like the general idea of it!
However, I am not really sure if adding stuff like this to
phoenix::actor doesn't break stuff.
Unfortunately i currently can't implement a feature like this, too
handicapped for such a big task. However, patches are very much
welcome!

>>> 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.

As noted in one of my other posts, the problem is not really if the
function is polymorphic or not. The problem is in variadic functions.
I can't see a way to automically guess which overload the user wanted
to call.


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