2016-10-19 3 views
6

Существует общая абстракция для обоих контейнеров и функций. Я узнал об этом в Haskell, и я пытаюсь реализовать его на C++.перегрузка над типами функций с шаблонами

Большинства C++ программисты знакомы с стандом :: преобразования, грубо говоря, заданную функцию от типа A к B, вы можете преобразовать контейнер типа А в контейнер типа B.

Вы можете преобразовать функции аналогичным образом, если задана функция foo от A до B, вы можете преобразовать функциональную панель с Z на A в функцию foo. бар от Z до B. Реализация проста, это просто композиция.

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

Контейнер легко (я знаю, что это не полностью вообще)

template <typename A, typename Func> 
auto fmap(Func f, vector<A> in) { 
    vector<decltype(f(in[0]))> out_terms{}; 
    for(auto vec : in) 
    out_terms.push_back(f(vec)); 
    return out_terms; 
} 

Однако аналогичная функция для функций делает меня гораздо более нервной.

template <typename FuncT, typename Func> 
auto fmap(FuncT f, Func in) { 
    return [f, in](auto x){ 
    return f(in(x)); 
    }; 
} 

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

Эта абстракция чрезвычайно общая, есть соответствующие fmaps для указателей на значения, которые, как я подозреваю, могут также конфликтовать.

Итак, я думаю, что мой вопрос: могу ли я иметь две различные реализации шаблонов с одинаковой подписью уровня шаблона? Я почти уверен, что ответ - нет, но может быть, что-то подобное может быть подделано. А если нет, какие инструменты доступны сегодня, чтобы различать перегрузки? Специально для типов функций.

Мне кажется, что это учебник для понятий, хотя я не уверен.

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

Редактировать 2: Тривиальный пример того, как это используется.

template <typename T> 
auto double_everything(T in){ 
    auto doublef = [](auto x){return 2*x;}; 
    return fmap(doublef, in); 
} 

Он обобщает карты над контейнерами на карты по «контейнерам». Таким образом, double_everything(vector<int> {1, 2, 3}) возвращает вектор с удвоенными элементами. Но double_everything([](int x){ return x + 1; }) возвращает функцию, выходные выходы которой в два раза превышают выходы функции приращения. Это похоже на удвоение своего рода списка. У абстракции есть некоторые приятные свойства, я не просто делаю это. Во всяком случае, переименование функции fmap для составления не отвечает на вопрос.

Edit 3: fmap для шаблона C принимает функции от A к B функциям от C<A> до C<B> и удовлетворяет fmap(compose(f, g) , c) = fmap(f, fmap(g, c)). Это свойство сохранения хорошей структуры.

Функции, которые делают это для диапазонов, уже существуют разными именами. Но диапазоны не являются единственными шаблонами для типов. Вот fmap для std::optional:

template<typename T, typename Func> 
auto fmap(Func f, optional<T> o) -> optional<f(*o)>{ 
    if(o) 
    return f(*o); 
    else 
    {}; 
} 

Эта реализация не предполагает каких-либо понятий диапазона вообще, как fmap для функций, представленных ранее. Но он удовлетворяет семантическим требованиям для fmap.

Я пытаюсь определить fmap для разных перегрузок таким же образом. Я бы определил новый operator * для пользовательского типа матрицы. Поэтому я бы с радостью определил fmap с точки зрения boost::transform_iterator. Тогда эти алгоритмы будут работать с функцией generic с точки зрения fmap.

Вот пример такой функции:

template < 
    template<typename, typename> class Cont, 
    typename Fmappable, 
    typename Alloc, 
    typename Func> 
auto map_one_deep(Func f, Cont<Fmappable, Alloc> c){ 
    auto g = [f](Fmappable x){ return fmap(f, x); }; 
    return fmap(g, c); 
} 

теперь, если мы пишем

auto lists = vector<vector<int> > { {1, 2, 3}, {4, 5, 6} }; 
auto lists_squared = map_one_deep([](int x){return x*x;} , lists); 

lists_squared печататься дает

1 4 9 
16 25 36 

Если мы вместо этого были вектор дополнительных опций, опциональные элементы были бы квадратными, если бы содержали элементы.

Я пытаюсь понять, как следует работать с функциями более высокого порядка в C++.

+0

Довольно точно, что это невозможно сделать в общем случае. Я бы просто переименовал вашу функцию 'compose'. – Barry

+0

Я уже не знаю достаточно C++ для оценки этого сообщения, но я думаю, что это по крайней мере актуально для того, что вы пытаетесь сделать: [The Functor Pattern in C++] (https://www.fpcomplete.com/blog/2012/ 07/the-functor-pattern-in-c) – chepner

+0

Вы можете ограничить тип своего шаблона для своей функции с помощью SFINAE. – Jarod42

ответ

0

Вот самый простой компромисс я нашел

template <typename FuncT, typename O, typename T> 
auto fmap(FuncT f, function<O(T)> in){ 
    return [f, in](T x){ 
    return f(in(x)); 
    }; 
} 

К сожалению, это требует, чтобы function<Output(Input)> украсить сайт вызова, и засоряет indirections. Я уверен, что это лучшее, что можно сделать, если требуется ограничение fmap.

Редактировать: You can do better. Ссылка дает возможность ограничить использование вызовов, что также в строках.

функция может быть записана следующим образом:

template <typename FuncT, typename T> 
auto fmap(FuncT f, tagged_lambda<T> in){ 
    return tag_lambda([f, in](T x){ 
    return f(in(x)); 
    }); 
} 

Вы можете выбрать версию, которую на месте вызова, позвонив по телефону

fmap(g, tag_lambda({}(int x){return x + 1;}));

или

fmap(g, function<int(int)>({}(int x){return x + 1;}));

Учитывая, как шаблоны работают, я уверен, что тегирование функции требуется.

Это сообщение в блоге, которое также говорит о проблеме и обсуждает другие варианты. http://yapb-soc.blogspot.com/2012/10/fmap-in-c.html.

1

Вы можете подделать его с помощью SFINAE, но не следует. Это вопрос стиля и идиомы.

Haskell все о типах классов, с программистом, ожидающим, что придется покрывать каждый тип всеми клубами, к которым он принадлежит. C++, напротив, хочет быть более неявным в определении возможностей типа. Вы показали «vector» и «произвольный вызываемый», но почему именно vector? Почему не произвольный тип контейнера? И этот произвольный тип контейнера, который я только что написал, имеет operator(), потому что причины. Итак, какой из них выбрать?

Нижняя линия, в то время как вы можете использовать трюки SFINAE для решения технических двусмысленностей, вы не должны использовать их для разрешения существенных двусмысленностей. Просто используйте два разных имени.

+0

'vector' кажется преждевременной оптимизацией. Чтобы подражать Haskel, это должен быть «список». –

+0

@Maxim Егорушкин 'vector' и' list' изоморфны, но вектор, как правило, лучше работает на процессорах. Поскольку C++ не реализует 'list' как тип алгебраических данных, он не так безопасен, как« список »Haskell. Так что, используя его по умолчанию, вы ничего не купили. Я не пытаюсь имитировать haskell, я пытаюсь использовать абстракции, которые я узнал из нее. – Polymer

Смежные вопросы