LINUX.ORG.RU

Аналоги функций высшего порядка в С++

 , ,


1

4

Допустим, некто попал на необитаемый остров без интернета, и у него только компьютер с про^W С++ - никаких функиональных языков. Ему хочется попробовать реализовать reduce, map, fold etc. самому и на С++. То есть должна быть функция, которая принимала бы контейнер, аккумулятор и операцию-функцию. Что ему делать? Указатели на функции? Функторы? Какие-то извращенские шаблоны или что еще можно придумать? Некто с удовольствием бы почитал манов на тему, но сразу как-то не нагуглилось.

★★★★★
Ответ на: комментарий от anonymous

Что не так с лямбдами в С++? На время компиляции их появление не повлияло

ну если ты сравниваешь с boost, который только с виду такой простой…

А по другому в языках со статической типизацией не бывает.

именно! ИМХО потому в C++ никогда и не будет нормальных лямбд.

Ты так и не объяснил, чем лямбды в С++ не полноценны? Приведи код что ли с лямбдами в другом языке, чтоб на С++ не ложился.

ну не знаю, насколько этот пример уместен, но я как-то не соображу, как на C++11 записать бесконечные последовательности, как их в SICP писали на схеме. Может просто мне не сообразить.

drBatty ★★
()
Ответ на: комментарий от drBatty

Нет, я сравниваю не с буст. От того, что ты напишешь лишнюю [](){} время компиляции не увеличится.

Ты считаешь, что в языках со статической типизацией нормальных лямбд быть не может? оО А как же SML, OCaml, F#, Haskell, Scala, ... да сотни их?

Бесконечные последовательности через лямбды? Не очень понял о чем ты, SICP читал. Код приводи=)

anonymous
()
Ответ на: комментарий от anonymous

нет. ты платиш за возможность ручного выбора необходимостью понимать страндарты под 1000 страниц где выбор поведения компилятором обоснован голосованием комитета ,а не следствием из аксиом языка.

как приземлёный пример : у кого С++ был первым языком программирования с ООП = считают(при отсутствии других источников о ООП) принципиальным наличия различения между виртуальными и обычными(которые на этапе компиляции разименовываются) методами - т.е за деревьями(необходимость иметь возможность в промышленном языке выбирать позднее или раннее связываения у методов обьектов(при том что обычные функции связываются всегда раним(можно конечно попресидать для позднего))) теряется лес (поздее связывание как неотьемлимое ООП)

qulinxao ★★☆
()
Ответ на: комментарий от qulinxao

Я, в общем-то согласен. Но таки NVI весьма интересная и трушная концепция(в плане разделения интерфейса использования от интерфейса наследования), а многие паттерны, вроде шаблонного метода, вытекают из нее сами собой. А по факту используют NVI только в С++ и D.

anonymous
()
Ответ на: комментарий от anonymous

имено . классы пересикаются с ООП.

stl кагбе родовое прогромирование.

qulinxao ★★☆
()
Ответ на: комментарий от qulinxao

с ООП = считают(при отсутствии других источников о ООП) принципиальным наличия различения между виртуальными и обычными(которые на этапе компиляции разименовываются) методами

при чём тут inline методы? это совсем другое. Ценность виртуальных методов в том, что они могут быть переопределены в другой единице трансляции, и в этом случае естественно не могут быть inline. Но совсем не из-за их виртуальности, а по своей сути.

Лично я не вижу другого метода изменения кода без изменения кода. Конечно кроме использования ВМ. Однако ВМ сама по себе несёт большие накладные расходы.

drBatty ★★
()
Ответ на: комментарий от anonymous

Ну, парадокс Блаба - ладно. Там есть и другие мифы, хотя cl является одним из моих любимейших языков.

Про мифическую нулевую стоимость. Считаю, что систему автоматического управления памятью (условно GC) никак не приделаешь без потери в производительности на определенных типовых задачах, если язык изначально не поддерживает и не ориентирован на такое управление. Но вопрос измерения производительности - очень сложная тема, где многое зависит от того, какие именно задачи рассматриваются и какие цели преследуются. «Эфемерные» и поколенческие сборщики мусора очень быстры, если создается много кратко-живущих объектов. Более того, такие сборщики умеют дефрагментацию памяти. Обогнать их на их же территории - дело очень сложное для программиста Си или Си++, может быть, неподсильное.

Что касается Boehm GC, если ты имеешь в виду его, то, судя по всему, он медленнее современных сборщиков мусора других языков. Не знаю как сейчас, но раньше в Mono использовали именно его. Там, где нужно было создавать много кратко-временных объектов, Mono тормозил прилично по сравнению с .NET. Я думаю, что одна из причин крылась в использовании Boehm GC.

В общем, на одних задачах будет эффективнее по производительности реализовать на языке со встроенным GC. На других - эффективнее без него на Си или Си++. Выбор не всегда очевиден. Причем, сам выбранный язык влияет на то, как формулируется задача, что еще больше усложняет предмет.

dave ★★★★★
()
Ответ на: комментарий от anonymous

Еще добавлю на счет похожего, но другого расхожего мифа. Можно, конечно, попытаться сымитировать на Си то, что делает JVM и ее сборщик мусора (Эдем, все такое), но человеки такой код не пишут... Его пишет машина. Вот, в чем проблема. А тот код, что пишут человеки на Си и особенно на Си++ далеко не всегда можно назвать эффективным по производительности.

dave ★★★★★
()
Ответ на: комментарий от dave

Да, я имел в виду этот сборщик. Он является единственным вариантом, способным в С/С++ работать с обычными указателями. Но в С++ мы можем сделать любой сборщик, если введем специальный тип указателя(ссылки) для автоматической сборки. Никаких проблем с С++ не будет.

anonymous
()
Ответ на: комментарий от dave

Однако код на С++ при прочих равных быстрее. Только на некоторых синтетических тестах java может победить. Если ты о jit, то он доступен и для С/С++, в llvm, например.

anonymous
()
Ответ на: комментарий от qulinxao
#include <iostream>
#include <functional>
#include <vector>

template <typename T>
struct stream {
    T first;
    std::function<stream()> rest;
};

template <typename T>
std::vector<T> take(std::size_t n, stream<T> stream)
{
    std::vector<T> result;
    for (; n > 0; --n, stream = stream.rest())
        result.push_back(stream.first);
    return result;
}

stream<int> f(int a, int b)
{
    return { a, [=]() { return f(b, a); } };
}

int main()
{
    auto xs = take(10, f(1, 2));

    for (auto &x : xs)
        std::cout << x << ' ';
    std::cout << std::endl;
}

/*
total heap usage: 16 allocs, 16 frees, 212 bytes allocated
 */
quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
Ответ на: комментарий от quasimoto

А если так:

void region()
{
    auto xs = f(1, 2);

    for (std::size_t i = 0; i < 1000000; ++i, xs = xs.rest())
        std::cout << xs.first << std::endl;
}

int main()
{
    region();
    for (std::size_t i = 0; i < 1000000; ++i)
        std::cout << "..." << std::endl;
    sleep(10);
}

то видно, что выделяется миллион лямбд, и они не собираются после выхода из region и вообще до выхода из программы, а target для std::function которые замыкания возвращает nullptr (HATE! то что std::function несовместим с сишными указателями на функции это само по себе проблема), так что в std::shared_ptr их не посчитать.

quasimoto ★★★★
()
Ответ на: комментарий от anonymous

Однако код на С++ при прочих равных быстрее.

От задачи зависит. Аллокатор у Java на порядки быстрее, чем new в C++. В задачах, где надо выделять много-много короткоживущих объектов Java любой язык без GC уделает запросто (и магия с пулами не спасет, у Java еще и компактификация есть).

Если ты о jit, то он доступен и для С/С++, в llvm, например.

Это не настоящий JIT, он никакой информацией из рантайма пользоваться не умеет, функции не специализирует, трейсы не анализирует. Да еще он и тормоз адов.

anonymous
()
Ответ на: комментарий от anonymous

Аллокатор у Java на порядки быстрее, чем new в C++. В задачах, где надо выделять много-много короткоживущих объектов Java любой язык без GC уделает запросто (и магия с пулами не спасет, у Java еще и компактификация есть).

пример задачи можно?

wota ★★
()
Ответ на: комментарий от wota

пример задачи можно?

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

anonymous
()
Ответ на: комментарий от anonymous

Такое всегда происходит, например, при функциональном стиле кодирования

возможно, но 99.99% кода на Java написано совсем в другом стиле

wota ★★
()
Ответ на: комментарий от anonymous

А кого это колышет?

всех, кто предпочитает решать задачи, а не витать в облаках

Разговор о том, когда GC быстрее ручного memory management.

потому я и спросил конкретную задачу, тут, например:

http://benchmarksgame.alioth.debian.org/

GC не быстрее

wota ★★
()
Ответ на: комментарий от qulinxao

Ну да, хороший пример, хотя и слишком теоретичен =) Через лямбды С++'ные действительно нельзя, т.к. они не могут быть по-настоящему рекурсивные, тем более если нужно шарить их. Но через функтор(в смысле С++) можно. Для начала сделаем «аналог» cons(а то в плюсах с этим не очень).

struct cons {
   string head;
   function<cons()> tail;
};

Ну а теперь твой dan_then_dog:

function<cons()> dan_then_dog()
{
   struct fun_t : enable_shared_from_this<fun_t> {
       cons operator()(string a, string b)
       {
           auto pf = shared_from_this();
           return cons{a, [=](){return (*pf)(b, a);}};
       }
   };
   auto pf = make_shared<fun_t>();
   return [=]() { return (*pf)("dan.jpg", "dog.jpg"); };
}

Есть что-нибудь более убойное? Чтоб смарты не справились.

anonymous
()
Ответ на: комментарий от quasimoto

Это был пример на лямбды и управление памятью. Так что ты сделал лучше меня(см. чуть выше), но слегка не в тему.

anonymous
()
Ответ на: комментарий от quasimoto

см. мой код. Да, только через лямбды не сделать, но через функторы - вполне.

anonymous
()
Ответ на: комментарий от anonymous

Аллокатор у явы(как и у любого языка с нормальным сборщиком) действительно быстрее СТАНДАРТНОГО плюсового(ты можешь написать миллион своих - и не обманывай, они помогают), но проявляется это только на синтетике. В реальных системах тебе нужно учесть работу самого сборщика, тормоза от его запусков, лишние выделения памяти и пр. Запусти какой-нибудь nginx, подолби его и следи за памятью и ответами. Ты никогда такого не достигнешь на Java. По крайней мере в ближайшее десятилетие.

Jit в LLVM может и плохой, но хороший сделать никто не мешает.

anonymous
()
Ответ на: комментарий от anonymous

см. чуть выше

А как использовать? Если так:

void region()
{
    auto xs = dan_then_dog()();

    for (int i = 0; i < 1000000; ++i, xs = xs.tail())
        std::cout << xs.head << std::endl;
}

int main()
{
    region();
    for (int i = 0; i < 1000000; ++i)
        std::cout << "..." << std::endl;
    sleep(10);
}

то поведение как и у меня — линейно растёт память сотнями мегабайт, после выхода из region она не освобождается и висит до выхода из main.

Такой энергичный код

object Test {

  // Неохота писать нормальный тип, пусть будет Any.
  def f(a: Int, b: Int): (Int, Any) = (a, (_: Unit) => f(b, a))

  def test {
    var xs = f(1, 2)
    for (_ <- 1 to 10000000) {
      println(xs._1)
      xs = xs._2.asInstanceOf[Unit => (Int, Any)]()
    }
  }

}

вообще никак на кучу JVM не влияет.

Такой:

d :: [Int]
d = 1 : 2 : d

main = go 10000000 d where
  go 0 _ = return ()
  go n (x:xs) = print x >> go (n - 1) xs

работает в постоянных 2MB RSS (хотя это уже чит, так как тут настоящий ленивый конвейер и лямбды не у дел).

quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)

абстрактный класс (интерфейс) с одним виртуальныам методом apply() - чем вам не функция?

и можно передавать экземпляры такого класса куда угодно.

BattleCoder ★★★★★
()
Ответ на: комментарий от quasimoto

У меня он освобождает память. И я не понимаю, как память физически может расти не линейно в данном случае. Надо разобраться и сделать так же.

anonymous
()
Ответ на: комментарий от quasimoto

Кто-нибудь понимает, почему в scala не растет память в этом примере?

anonymous
()
Ответ на: комментарий от quasimoto

Что значит не влияет на кучу? Просто кучи хватает для него. Нету запусков GC.

anonymous
()
Ответ на: комментарий от quasimoto

то видно, что выделяется миллион лямбд, и они не собираются после выхода из region и вообще до выхода из программы,

проверил - потребление ОЗУ стабильно 170Кб на всем выполнении программы для gcc 4.7.2, аналогично clang 3.2 - стабильно 184Кб, или я вообще не о том?

wota ★★
()
Ответ на: комментарий от quasimoto

Что ты обманываешь. Все там освобождается... Более того, ненужные объекты cons уничтожаются после каждой итерации цикла...

anonymous
()
Ответ на: комментарий от drBatty

«разименовать метод» тут харкоженый адресс ( а уже как возможность пипхола инлайлинг) метода - т.е ранее. взятие значения во время исполнения - обращение к структуре асоциированой [с классом асоциированым] с обьектом - позднее.

именно. в С++ подчёркивается особость виртуальных на фоне обычных(унаследованных от С раннесвязываемых) . подчёркивается наличием специального приседания для создания виртуального и отсутствия какого-либо при создании «обычного»

qulinxao ★★☆
()
Ответ на: комментарий от wota

Я из под valgrind-а забыл вытащить, а так даже с моим примером и с -O0 всё хорошо — стабильно в 1MB RSS.

quasimoto ★★★★
()
Ответ на: комментарий от anonymous

суть притензии не к С++ как таковому (мир Страустропу)

суть к заборостроительным в которых ООП преподают не просто с помощью С++ но при помощи С++ с забаном виртуальных ибо «продвинутая тема»

qulinxao ★★☆
()
Ответ на: комментарий от anonymous

в ооп ранее связывание есть необязательный бантик - который полезен в промышленном языке ибо скор.

а так как С++ на основе С (который по умолчанию ранеесвязывающий) то по «хорошему»(что приведёт к ещё более ужасным последствиям чем ща) в С++ все методы могли бы быть виртуальными по умолчанию - но это не позволило бы С++ взлететь как он взлетел.

qulinxao ★★☆
()
Ответ на: комментарий от qulinxao

C++ - это язык с поддержкой ООП, а не ОО-язык и классы там нужны не только для ОО. Зачем не ОО-классам виртуальные методы?

anonymous
()
Ответ на: комментарий от tailgunner

В первом приближении - нет. Т.к. ты создаешь новый объект(лямбду), который ссылается на текущую функцию(которая уже расшарена к этому моменту и на которую уже имеются внешние ссылки). Т.е. через unique_ptr может и можно сделать, но как-то совсем иначе.

anonymous
()
Ответ на: комментарий от anonymous

Аллокатор у Java на порядки быстрее, чем new в C++.

ага. и память оно жрёт гигами. И по своему серверу на каждую жабу надо. Юзай свой new (его можно перезагружать в C++)

drBatty ★★
()
Ответ на: комментарий от anonymous

когда GC быстрее

когда мусор убирать не надо, очевидно же!

drBatty ★★
()
Ответ на: комментарий от drBatty

Ты невменяемо ничтожное ламерище, как всегда.

Просвещать тебя, ничтожную грязь, считаю делом безнадежным.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.