Реализация и обёртывание композиции функций в C ++ для ленивых вычислений - PullRequest
4 голосов
/ 12 апреля 2019

Предположим, у меня есть наивная реализация applicative, то есть имени, которое я выбрал для здравомыслия, но не знаю, что я знаю что-то о классе Applicative из других языков.Вот реализация:

#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"; });
} 

Теперь это можно улучшить многими способами.Предоставляя что-то вроде make_applicative для создания на месте, пытаясь исключить избыточные копии и перемещения, если они есть, и т. Д. Но с тех пор, как я увидел нашу способность создавать функции в C ++, я чувствую, что возможно что-то лучшее.Любая реализация compose будет работать, поэтому давайте выберем одну из них в этом codereview.stackexchange вопросе.С его помощью мы можем сказать что-то вроде

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)

Довольно мило.Возвращаясь к моей идее, я думаю, что это должно сделать возможным переработку моей applicative реализации следующим образом:

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"; 

Как видите, это довольно лениво оценивается, и мы только предоставляем значениекогда нам это нужно, с eval_with.Я думал о том, как реализовать эту новую версию в течение часа, и я понятия не имею, где хранить операции и составные функции, что делать из параметра шаблона applicative, как у нас здесь с std::string, и еще много проблем,Как реализовать что-то подобное?Это тривиально, как я изначально надеялся, или требует много кода?Я действительно хочу этого, потому что я чувствую, что это принесло бы мне много пользы, не позволяя множеству аргументов передавать длинную цепочку функций.

Редактировать: я работаю над этим немного больше и получаю реализацию composeЯ связал не то, что я имел в виду, так как мы все еще выполняем все вызовы функций в цепочке и передаем аргументы.Но вы можете ответить, предполагая, что любая функция compose будет работать, и выбор лучшей реализации compose будет оптимизацией производительности.То, что я имел в виду, было больше похоже на следующее, взятое из моего примера

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"; });

Эти then вызовы приведут к одному вызову функции, эквивалентному (s.size() * 4) - 2, который можно оценить с помощью eval_with.

1 Ответ

2 голосов
/ 12 апреля 2019

Это то, что вы хотите?

#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"; 
} 

Отказ от ответственности: я не беспокоился об идеальной пересылке, поэтому все передается по значению.

...