
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.