Reputation: 1526
Suppose I have a naive implementation of an applicative
, which is a name I picked for sanity and not that I know anything about Applicative
type class from other languages. Here goes the implementation:
#include <iostream>
#include <string>
template <typename T>
struct applicative {
template <typename Fn>
auto then(Fn f) const {
return applicative<decltype(f(data_))>{f(data_)};
}
template <typename Fn>
auto and_last(Fn f) const {
return f(data_);
}
T data_;
};
int main() {
applicative<std::string>{"hello world"}
.then([](std::string const& s) {return s.size() * 4; })
.then([](int k) {return k - 2; })
.and_last([](int k) { std::cout << k << "\n"; });
}
Now, this could be improved in many ways. Providing something like make_applicative
for in-place construction, trying to eliminate redundant copies and moves if there are any etc. But from the time I saw our ability to compose functions in C++, I feel like something better is possible. Any compose implementation would work, so let's pick the one in this codereview.stackexchange question. With it, we can say things like
auto f1 = [](std::pair<double,double> p) {return p.first + p.second; };
auto f2 = [](double x) {return std::make_pair(x, x + 1.0); };
auto f3 = [](double x, double y) {return x*y; };
auto g = compose(f1, f2, f3);
std::cout << g(2.0, 3.0) << std::endl; //prints '13', evaluated as (2*3) + ((2*3)+1)
Pretty nice. Going back to my idea, I think this should make possible a rework of my applicative
implementation in following way:
auto sf = applicative<std::string>{}
.then([](std::string const& s) {return s.size() * 4; })
.then([](int k) {return k - 2; });
std::cout << sf.eval_with("hello world"); << "\n";
As you can see, this is sort-of lazy evaluated and we only supply value when we need it, with eval_with
. I've been thinking about how to implement this new version for one hour now and I have no idea where to store operations and composed functions, what to make of applicative
template parameter like we have here with std::string
and many more problems. How would one implement something like this? Is it trivial as I initially hoped to be or does it require a lot of code? I really want this because I feel like this would buy me a lot by preventing a lot of argument passing on long chain of functions.
Edit: I am working on it a little more and turns out compose
implementation I linked is not what I actually had in mind since we are still performing all the function calls in the chain and still passing arguments around. But you can answer assuming any compose
function would work and choice of a better compose
implementation would be a performance optimization. What I had in mind was more like following, taken from my example
applicative<std::string>{"hello world"}
.then([](std::string const& s) {return s.size() * 4; })
.then([](int k) {return k - 2; })
.and_last([](int k) { std::cout << k << "\n"; });
Those then
calls would result in a single function call equivalent to (s.size() * 4) - 2
which could be evaluated with eval_with
.
Upvotes: 4
Views: 289
Reputation: 2324
Is that what you want?
#include <iostream>
#include <string>
struct id
{
template <typename T>
auto operator()(T t) const
{
return t;
}
};
template <typename T, typename Func = id>
struct applicative {
applicative(Func f = Func())
: _f(f)
{
}
template <typename Fn>
auto then(Fn f) const {
auto composition = [=](T val) { return f(_f(val)); };
return applicative<T, decltype(composition)>(composition);
}
auto eval_with(T t)
{
return _f(t);
}
Func _f;
};
int main() {
auto sf = applicative<std::string>{}
.then([](std::string const& s) {return s.size() * 4; })
.then([](int k) {return k - 2; });
std::cout << sf.eval_with("hello world") << "\n";
}
Disclaimer: I didn't bother about perfect forwarding, so everything is passed by value.
Upvotes: 2