Pull to refresh

Comments 101

Хорошая заметка.

Однако, я пожалуй выбираю старый добрый for :)

int sum = 0;
for(int i = 0; i < candles.size(); i++)
    sum += candles[i].ClosePrice;


Потому, что если даже через 5 лет, я вернусь к этому коду, я моментально пойму, что он делает. А иначе, мне придется вспоминать особенности библиотеки или что это тут какая-то лямбда функция делает. И уж тем более, сторонний человек тоже быстрее поймет, что делает простой for.

Не очень понимаю я стремление к минимализму. С одной стороны сжато и красиво, но каков практический смысл? Человек большую часть времени читает код. И, к сожалению, часто чтение и осознание сжатого кода занимает больше времени, чем если бы он был записан «влоб».

P.S. Естественно я не агитирую писать длинный код. ;)
Практический смысл в том, чтобы декларативно запрашивать данные из массива или вектора. Я не думаю что код, написанный с использованием LINQ труднее для понимания. Чем меньше кода написано — тем меньше ошибок допущено) Если человек знаком с SQL — понять код на LINQ не составит труда…

После программирования на C# — отсутствие LINQ для C++ — существенный минус. Теперь мне намного проще писать на C++…
Даже для самых присамых императивщиков несекрет, почему декларативный стиль называется декларативным, и в чем его непоколебимый плюс в отображении сути кода.
Практический смысл (и огромный) в компакности кода и его читаемости. Декларативно-функциональные инструкции на LINQ выглядят куда легковеснее наборов «for» и «foreach». Я уже молчу про такие вещи, как .SelectMany().
Я на днях пробовал играться с Kotlin (JVM-based язык), и был неприятно удивлени отсутствием старого доброго for-a. Поигравшись немного, понял, почему они его убрали.

В Kotlin проход по индексам осуществляется выражением

for( val index: array.indices )

таким образом, нет типовых ошибок с «i < a.size()» vs. «i <= a.size()»

Это к тому, что старый добрый for, возможно, устарел :-)
что делать при реализации чуть более сложной логики, чем проход по всем элементам массива? Скажем я хочу пройти по всем нечетным:
for(int i=1; i < size; i+=2)
Насколько я понял, там вводится механизм итераторов (алгоритмов обхода коллекций). Например, реверсивный обход делается так:

for( val index: array.indices.reverse )

Но я глубоко не вникал, так по верхушкам посмотрел…
Но ведь for используют не только для коллекций. Может мне нужно факториал посчитать или еще что-то… Какую-то хитрую сумму или еще что-то математическое. Не только же вектора гонять им
Ну в 98% случаев его используют, чтобы «вектора гонять» :-)
А для оставшихся случаев есть while.
В C# есть возможность написать
foreach(var index in Enumerable.Range(3,456))
{ 
Console.WriteLine(index);
}

Возможно и в Котлине есть подобные механизмы.
Есть:
for(int i : new IterableRange(1, 1)) 
{ 
    System.out.println(i); 
}
Не проблема сделать такую в библиотеке. Как-то не пользовался ни разу…
Спасибо, добавим)
согласен. автор молодец, наверняка кому-то будет более лицеприятен LINQ-подобный код, но я бы в реальных проектах не стал бы им грешить, все-таки очень различается уровень разработчиков; если писать на С++ умеют многие, то читать и понимать, а тем более модифицировать — единицы. лучше бы облегчить им задачу.

я даже бустом стараюсь пользоваться аккуратно, если нужен regex_match — пожалуйста, но если BOOST_FOREACH, то лучше вписать обычный for, пусть и не так компактен, зато и при беглом взгляде понятно что происходит.
Полностью поддерживаю
а почему «LINQ to Entities»? Вы изобразили «LINQ to Objects». А если честно, то причём здесь LINQ вообще, если это просто функции высшего порядка? :)
Вы правы, спасибо — поправил. Название было написано раньше статьи и так и осталось в черновом варианте))
Вот только у вас получился не Linq2Entities, а Linq2Objects: у вас нет той вещи, которая, собственно, и делает LINQ настолько мощным — AST.
Ну да, это бы убило типизацию C++. Я старался её сохранить.
Название статьи я поправил, выше уже указали на ошибку.
Ага, и настолько же «протекшим», ибо каждый IQueryProvider разбирает синтаксическое дерево так, как ему заблагорассудится. Ну это так, к слову.
Это правда. Но сам механизм, позволяющий получать и передавать выражения, чтобы выполнять их так, как нужно, был и остается очень полезен. Именно на нем строится дикое количество современного кода.
С этим не спорю. Как абстракция, LINQ — fail, но как инструмент — просто офигенен.
Сам предпочитаю его любым другим способам записи (причем в «первозданном» виде — без ключевых слов, а в виде вызовов Extension-методов).
Я, кстати, тоже предпочитаю method chain. Причина проста — не все можно выразить через ключевые слова, а смесь обоих подходов в одном коде выглядит ужасно.
Это да, но кроме этого еще и читабельность. Запись «from item in someCollection select ...» мне категорически не нравится. Если бы где-то фигурировало «from someCollection» было бы понятно, но «from item» — какого лешего.
Потому что это do-нотация хаскеля, заделанная под что-то sql-подобное путем замены слов. А в хаскеле пишется именно в таком порядке, если не ошибаюсь, то будет
item < — someCollection; return…
Вот как раз такая запись меня ничуть не смущает. Смущает именно «from item». Непонятно почему «из» item то что-то типа выбираем, когда работаем с коллекцией.
Это, конечно, все бесспорно круто… но… зачем? :) Признаюсь, мне всего два раза в жизни хотелось что-то подобное. В остальных случаях C++11 и STL (ну и в крайнем случае boost) прекрасно справлялись. Не часто требуется задача, в которой надо сразу применить кучу фильтров.

Вообще, основная проблема здесь в том, что в C++ нет функциональщины. Ему нужен map и filter. Ох, как их порою не хватает.
map — std::for_each, fold — std::accumulate
PROFIT
map — std::for_each

Неужто мне тебя учить? Ты же любишь Haskell, ты должен знать, что в контексте функционального программирования переменных нет — есть только константы. А значит, std::for_each не должен менять существующий контейнер, а должен возвращать новый.

std::accumulate

Вообще не понятно к чему это ты приплел. Я за fold хоть что-то сказал? Я говорил за filter.
Неужто мне тебя учить? Ты же любишь Haskell, ты должен знать, что в контексте функционального программирования переменных нет — есть только константы. А значит, std::for_each не должен менять существующий контейнер, а должен возвращать новый.

Не, учить меня не надо пока. Если тебе хочется новый контейнер, то std::transform.

Вообще не понятно к чему это ты приплел. Я за fold хоть что-то сказал? Я говорил за filter.

Неужто мне тебя учить? )) Фильтр же — это свёртка списка.

template <typename Container, typename InputIterator, typename UnaryPredicate>
Container filter(InputIterator _from, InputIterator _to, UnaryPredicate _pred)
{
	Container collection;
	return std::accumulate(_from, _to, collection, 
		[_pred] (Container acc, const InputIterator::value_type & val) -> Container
		{
			if (_pred(val))
				acc.insert(std::end(acc), val);
			return acc;
		});
}

//////////////////////////////
// usage

	std::vector<int> vec = {0, 1, 2, 3};
	std::vector<int> newVec = filter<decltype(newVec)>(std::begin(vec), std::end(vec),
		[] (int n)
		{
			return n % 2 == 0;
		});


Парочку оптимизаций сделать и всё. Ну или можно через итераторы опять же переписать. Юзать back_inserter какой-нить.
Я тупанул собсно. Можно даже без возвращаемого типа обойтись. Всё равно он будет такой же, как и тип входной коллекции.
Насколько я понял, посмотрев исходники автора, основная фича (кроме синтаксического сахара) в том, что никаких промежуточных контейнеров во время выполнения всего «linq»-выражения не создается, только конечный, во время выполнения toSomething.

where, select и т.п. формируют что-то типа АСТ
Да, where, select и прочие наворачиваются на итераторы и исполняются отложенно.
Это понятно. Мой код актуален лишь в контексте обсуждения, а не статьи. Boolinq мне понравился.
Увидев всё это я начал делать boolinq…
Я тоже однажды сел писать то же самое, что написали Вы. Если что, pull-реквесты принимаются? )
По устному согласованию могу добавить в список коммитеров.
У меня была одна идея, когда я начинал писать. Но потом как-то времени не хватило. Если она будет реализуема в контексте библиотеки, то можно. Я посмотрю исходнички подробнее и отпишусь в твиттере, если что.
Вы просто не пропадайте) Идея была по какой-то специфичной функции или концептуальная?
Идея была концептуальная, но вполне возможно, что она уже реализована. Я просто посмотрю сорцы и подумаю :)
Если тебе хочется новый контейнер, то std::transform.


Да брось! Это же неудобно. Да и новый контейнер предварительно придется создавать, т.к. transform принимает OutputIterator, из-за чего нельзя будет делать несколько map'ов и filter'ов в одну строку. :)

Неужто мне тебя учить? )) Фильтр же — это свёртка списка.


Ты всерьез думаешь, что делать filter через std::acumulate — это нормально?)

Вопрос стоит в удобстве, а не в возможности. В противном случае, библиотеки подобного типа и не нужны были бы вовсе. :)
Если вопрос для тебя стоит в удобстве, то лучше в С++ вообще функциональщину не тянуть. Проще заюзать Haskell, в котором всё это отлично реализовано. А если хочется изврата, то Boost.Phoenix спецом для тебя.

Ты сказал, что проблема в том, мол, не хватает якобы map и filter. Я ответил, что проблемы никакой нету, ибо map на самом деле имеется, только называется по-другому и выполнен в STL-style. А filter может и нету, но он легко реализуется через свёртку списка, если уж функциональщину брать за основу.
Очевидно, имелось в виду, не хватает удобного map и filter.
А то так можно и про лямбды сказать «делайте struct с operator ()»
Собственно, именно про удобство всегда и говорят, ибо иначе нечего обсуждать, языки Тьюринг-полные.
Ладно, по поводу фильтра я может ещё соглашусь. Но map-то зачем какой-то другой?
Ну с учётом наличия лямбд for_each вполне удобен.
Фильтр же — это свёртка списка.

Фильтр — это фильтр. То, что в где-то он определен через фолд не значит, что так же нужно делать в С++. В С++ так делать зло и если охота, нужно подумать, а стоило ли вообще С++ выбирать.
std::remove_copy_if, который вы тут навелосипедили через std::accumulate, нужно реализовывать через цикл, будет смотреться намного чище.
ikalnitsky попросил функциональщины — я дал ему функциональщины. Не нужно мне говорить, что я и где навелосипедил. Или Вы думаете, что у меня в production такой код? ))
Признаюсь, мне всего два раза в жизни хотелось что-то подобное.

И один из них, дай-ка угадаю, был в этом треде? :)
Уупс… Ошибочка, тогда три раза. ;)
Я тут заметил, что используется только #pragma once, а он, к сожалению, не является стандартным. Поэтому, если хочется оставить преимущества последнего и не пренебрегать стандартом С++, лучше воспользоваться следующей конструкцией:

#pragma once
#ifndef BOOLINQ_H_
#define BOOLINQ_H_
 
// paste boolinq code here
 
#endif // BOOLINQ_H_
Все популярные компиляторы поддерживают once
Все популярные компиляторы поддерживают RVO и прочие распространенные оптимизации. Вы пишите код, который сразу рассчитывает на оптимизацию и некорректен с выключенной оптимизацией? :)

Я люблю писать по максимуму в рамках Стандарта C++.
К тому же, даже разработчики MS в своей реализации STL делают подобный финт: include guard + pragma once.
Не знаю, к чему относится первая часть комментария. Но, отвечу, — нет.
По поводу MS разработчиков — если они так делают, вовсе не означает, что так нужно делать всем. Не вижу причин не использовать once, если нет желания поддержки экзотических компиляторов
В бусте по-моему #pragma once включается спецом для VS препроцессором, если мне память не изменяет.
Зачем тогда оставлять pragma? Насколько это действительно оправдано. Какие компиляторы этого не поддерживают? В библиотеке используется C++11. Я думаю это предъявляет более жесткие требования к используемым компиляторам…
pragma once якобы быстрее. Как я указал в комментарии выше — разработчики MS в своей STL юзают и pragma и include guard.
Типа да. Для инклуд-гардов препроцессору по ходу всё равно нужно прочитать весь файл.
Не, ну если в STL так кодят, то это уже совсем другой разговор) Тогда это можно просто считать хорошим стилем. Я думал уже все развивающиеся компиляторы тянут эту возможность…
Компиляторы может и тянут, но разработчики библиотек следуют правилу «Доверяй, но проверяй». В стандарте прагмы нету.
Для двух полей можно так:
struct S
{
    int f1,f2;
};
vector<S> v;
int sum=std::accumulate(v.begin(),v.end(),S(),[](S x, S y){S t; t.f1=x.f1+y.f1; return t;}).f1;

И это речь идёт о подсчёте суммы… Согласитесь, жесть. А если их надо перемножить или дисперсию посчитать?
Согласен, но я не знаком с концпецией LINQ и с вашей реализацией, и для меня код, предложенный вами тоже не совсем очевиден. Для меня цикл тут видится наиболее логичным решением. Возможно вам стоило выбрать пример, на котором преимущества были бы более очевидны.
Чтобы понять, лучше конечно бы познакомиться, а если серьёзно, то разве не понятно все с названия концепции «декларативное программирование»?
int sum = 0;
for (Candle candle : vector) 
    sum += candle.ClosePrice;


".., но Visual Studio 2010 увы эту фичу не поддерживает:" — бздёшь и провокация :-)

 for each(auto candle in vector)
     sum += candle.ClosePrice;

 for each(Candle candle in vector)
     sum += candle.ClosePrice;

 for each(Candle& candle in vector)
     sum += candle.ClosePrice;
Интересно, в каком стандарте это описано.
Это глючный нестандартный extension Visual C++
Вы мне открыли глаза о_О
Хотя именно с++11 фичу она не поддерживает, for each в студии это CLR фича, работающая в нейтив коде по непонятным причинам…

Microsoft не раз говорил что они не рекомендуют ей пользоваться, она глючная и не поддерживается в других компиляторов.

Лучше используйте BOOST_FOREACH или подождите Visual Studio 11, там будет поддерживаться range-based for из C++11:
std::vector<int> numbers;

for(int i : numbers){
// blah blah
}
Написано хоршо, я бы отправил в буст после допиливания :)

Вы могли бы в статью добавить освещение того, как оно работает изнутри, чтобы можно было понять насколько оно медленнее выполняется по сравнению с обычным кодом? Я немножко смотрел исходники, вроде бы там все достаточно хорошо в этом плане, но все-таки хотелось бы увидеть комментарии в удобном для восприятия формате, хотя бы основные идеи — в сжатой форме.
Думаю дописать принципы работы. Там есть несколько особенностей интересных.
В бусте что-то похожее есть, но синтаксис конечно, пугает и мало реализовано функций…
Особенно хочется понять как работает сортировка…
Сначала код был очень простой. Сложным он стал когда надо было первые значения front() и back() тоже отложенно вычислять, чтобы была возможность работать с Input Iterator
Допишите, допишите, все дописывайте. Очень крутая штука. (А меня особенно отложенность интересует.)
Крайне приятная реализация, очень интересна портируемость на GCC. Как я понимаю, пока все проверялось в рамках Visual C++.
Проверял на GCC. Для меня это приоритетное направление. Даже держал PRО-файл для QtCreator — боюсь он уже устарел порядком… Надо бы обновить. Часто приходится писать в креаторе…
Думаю есть смысл добавить это в описание на гугло-коде, а то я уже почти сел собирать/проверять (наверное и не я один)
Скажем так, последний раз проверка на совместимость с GCC была ой как давно, но я обязательно попробую на днях — и в случае чего — поправлю несовместимости…
на gcc 4.7 работает только после некоторых правок
Помнится специально для GCC ставил пробелы между "> >" у вложенных шаблонов. Студия и без пробела проглатывает. Поправлю на днях для GCC, обещаю)
То есть в C++ тоже ещё нет более-менее полноценной реализации LINQ? Что ж, значит, PHP — не единственный язык, в котором все библиотеки а-ля LINQ кошмарны. :) Всё хочу на досуге версией для похапэ заняться, потому что без слёз на текущие библиоткеи смотреть нельзя.

Если реализовать большинство методов LINQ, то это будет офигительно круто. Причём хорошо, что учитывается специфика языка (тяга к bytes и bits в C++, например). Очень показательна в этом плане аналогичная библиотека для JavaScript (там преобразование между объектами и массивами и прочие странности, активно использующиеся в языке), на совесть сделана.

После появления лямбд с плюсах не может не появиться LINQ to objects. Это мощнейший инструмент, после использования которого в шарпе не представляешь, как можно жить без него. :)

Успехов!
Для PHP есть Underscore.PHP, вполне вменяемая замена ужасным функциям стандартной библиотеки. Не LINQ безусловно, но рекомендую взлянуть прежде чем начинать писать код.
Посмотрел, ленивых вычислений нет. Без них это уже совсем не LINQ. Уже не получится написать:

from(naturalNumbers).select(i % 2 == 0) // нет бесконечности
from(array).where(i > 0).any() // будет полный проход

Как замена стандартным функциям тоже не подходит, реализовано слишком малое количество функций.
а можно пример использования groupBy? как привести его результат к определенному контейнеру?
Допишу сегодня
Прикольно, пожалуй запомню :) Кстати, а с clang'ом оно как? Я так понимаю ждать 3.1?
Не пробовал, но планы конечно на все компиляторы, поддерживающие C++11.
Я вот подумал, а что если переоформить код в соответствии с Qt coding conventions и запульнуть на qt-project? Штука весьма удобная и очевидная.
А потом переоформить в соответствии с конвенциями Boost и запульнуть туда?
Крутая штука, которая просто обязана была появиться в C++11. Есть какой-то опыт использования boolinq в продакшене?
В своих маленьких рабочих проектах использую. Она неплохо покрыта тестами. Это придаёт уверенности.
На GoingNative говорили, что стандартная библиотека нуждается в новом функционале от сторонних разработчиков. Я бы ОЧЕНЬ хотел видеть что-то подобное в стандартной библиотеке. Вы бы не могли отправить ее комитету? Это будет стоить Вам многих эфортов и даст Вам мало профита, но все же я надеюсь.
Спасибо за работу, но нет до конца доверия вашей библиотеке…
Поэтому for…

RuStarter — краудфандинг по-русски
На мой взгляд модульные тесты должны обеспечивать доверие, а они есть.
еще такой вопрос, насколько эффективно работать с большими векторами? в коде проскакивали push_back… выделяется ли память под размер, например, результата отработанного where, или же могут происходить перевыделения памяти во время его отработки? если не выделяется, то есть ли пути это реализовать?
ну, еще по мелочи, возможность достучаться до индекса элемента тоже оказалась бы полезной)
Заранее память не выделяется, но это чисто реализация toVector(). Можно ведь добавить ей необязательный параметр… Думаю сделать перегрузки всех методов но с двумя парметрами: индексом и значением… как бы это сделать не копируя весь код…
Вот что делает с людьми возвращение к C++ из C#. К удобному привыкаешь быстро.
Sign up to leave a comment.

Articles

Change theme settings