Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>По факту D Range нормально подходит только для SinglePass.
Пока остальное отпущу.
Но по этому у нас кажется сильное недопонимание, основной плюсs range это легкая
комбинируемость функций с ними работающих и ленивость, все это по моему легко
перекрывает недостатки.
import std.stdio;
import std.range;
import std.algorithm;
void main()
{
iota(0, 10).map!(x => x *x).filter!(x => x % 2).writeln;
}
Здравствуйте, FR, Вы писали:
FR>Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>>По факту D Range нормально подходит только для SinglePass.
FR>Пока остальное отпущу.
FR>Но по этому у нас кажется сильное недопонимание, основной плюсs range это легкая FR>комбинируемость функций с ними работающих и ленивость, все это по моему легко FR>перекрывает недостатки.
FR>
FR>import std.stdio;
FR>import std.range;
FR>import std.algorithm;
FR>void main()
FR>{
FR>iota(0, 10).map!(x => x *x).filter!(x => x % 2).writeln;
FR>}
FR>
Одно другому (в смысле, диапазоны итераторам) не мешают.
Диапазоны хороши, когда надо по ним пройтись один раз и забыть. А итераторы хороши собственно для итерирования, которое может быть каким угодно хитрым. Но после итерирования берешь пару итераторов, заворачиваешь ее в диапазон и вперед — это, что делает boost::range, и твой пример элементарно на нем переписывается:
Здравствуйте, FR, Вы писали:
FR>Но по этому у нас кажется сильное недопонимание, основной плюсs range это легкая FR>комбинируемость функций с ними работающих и ленивость, все это по моему легко FR>перекрывает недостатки.
Есть три сущности:
1. итераторы, как в STL
2. Range обвёртки над итераторами, как в Boost.Range или в Adobe ASL
3. D Range — в них нет никаких итераторов, есть только ranges
Комбинируемость есть и в 3 и в 2. А вот недостатки которые я выше перечислял — относятся только к 3.
FR>
FR>void main()
FR>{
FR>iota(0, 10).map!(x => x *x).filter!(x => x % 2).writeln;
FR>}
FR>
Пример комбинируемости range обвёрток над итераторами:
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>>>Для стандартных массивов перегрузки в библиотеке или в языке? FR>>В языке.
EP>О чём и речь — что-то для range'ей уже встроено непосредственно в язык.
Нет там ничего для range обычная перегрузка операторов.
Массивы были в самой первой версии языка, в то время когда у Алекандреску с его rangeманией
была совсем другая любимая игрушка
FR>>Согласен, но в таких случаях часто ленивость спасает.
EP>У нас чистый permutation алгоритм — std::partition. В Phobos'е лишний раз дёргаются ручки у range'а, как тут поможет ленивость?
Тут возможно и нет.
FR>>По моему у тебя идея фикс "новые range бесплатно" ,
EP>Не только у меня — смотри выше что пишет A.A. про std::find — чтобы получить первую часть, нужно делать реализовывать новый тип range — Until, в то время как в STL он действительно получается бесплатно: [first, find(first, last, x))
Думаю, пока как выкрутится
Пока как идея реализовать кроме range еще и zipper думаю поиск и многое другое он вполне закроет.
Здравствуйте, FR, Вы писали:
EP>>>>Для стандартных массивов перегрузки в библиотеке или в языке? FR>>>В языке. EP>>О чём и речь — что-то для range'ей уже встроено непосредственно в язык. FR>Нет там ничего для range обычная перегрузка операторов. FR>Массивы были в самой первой версии языка,
То что они были это понятно, речь о том что их адаптировали к range.
FR>в то время когда у Алекандреску с его rangeманией была совсем другая любимая игрушка
Какая?
FR>>>По моему у тебя идея фикс "новые range бесплатно" , EP>>Не только у меня — смотри выше что пишет A.A. про std::find — чтобы получить первую часть, нужно делать реализовывать новый тип range — Until, в то время как в STL он действительно получается бесплатно: [first, find(first, last, x)) FR>Думаю, пока как выкрутится
Пока думаешь, можешь любоваться на:
template<typename I, typename T>
I find(I first, I last, const T& x)
{
while(first != last && *first != x)
++first;
return first;
}
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>>>Не только у меня — смотри выше что пишет A.A. про std::find — чтобы получить первую часть, нужно делать реализовывать новый тип range — Until, в то время как в STL он действительно получается бесплатно: [first, find(first, last, x)) FR>>Думаю, пока как выкрутится
EP>Пока думаешь, можешь любоваться на
EP>
EP>template<typename I, typename T>
EP>I find(I first, I last, const T& x)
EP>{
EP> while(first != last && *first != x)
EP> ++first;
EP> return first;
EP>}
EP>
Свой тип не нужен, нужные типы генерятся сами при использовании комбинаторов из стандартной библиотеки.
auto firstPart(R,T)(R xs, T x)
{
auto n = xs.save.countUntil(x);
if (n < 0) return xs;
return xs.takeExactly(n);
}
Те же три строчки, та же сложность, меньше мест для ошибки.
(и нет, тут нет двухкратного прохода)
Здравствуйте, Evgeny.Panasyuk, Вы писали:
DM>>Но не только это, а выполнение почти произвольного кода. Прочитать файл, отсортировать, распарсить, посчитать... EP>Это действительно круто(так же круто как и mixins), но эта фича слабо связанна с концепциям
Потому что концепции — слабое решение более общей проблемы — компайл-тайм проверки входных параметров на нужные свойства и выбор действий в зависимости от этого. Так вот, подобные проверки в D всегда будут мощнее плюсовых за счет встроенного интерпретатора.
DM>>Проверить компилируемость выражения, опять же, и в зависимости от результата делать осмысленные вещи. EP>В каком виде? В смысле только SFINAE-like или, допустим, можно поймать static_assert где-то в глубине call-stack'а?
Можно в любом месте кода написать что-нибудь подобное:
static if (__traits(compiles, x + 1))
writeln("number");
else
writeln("owl");
Где вместо "х + 1" может быть любое выражение.
EP>4. Какой тест позволит различить InputIterator и ForwardIterator?
В стандартной либе есть предикаты вроде isForwardRange, внутри там обычный hasMember, т.к. подобные штуки имеют разные наборы функций.
Здравствуйте, D. Mon, Вы писали:
DM>Здравствуйте, Evgeny.Panasyuk, Вы писали:
DM>>>Но не только это, а выполнение почти произвольного кода. Прочитать файл, отсортировать, распарсить, посчитать... EP>>Это действительно круто(так же круто как и mixins), но эта фича слабо связанна с концепциям
DM>Потому что концепции — слабое решение более общей проблемы — компайл-тайм проверки входных параметров на нужные свойства и выбор действий в зависимости от этого. Так вот, подобные проверки в D всегда будут мощнее плюсовых за счет встроенного интерпретатора.
Концепции — это не только проверки, это еще и type classes из Haskell. Плюс соответствующие перегрузки/специализации. Ты не офигеешь все это явно кодить на D-шном интерпретаторе?
DM>>>Проверить компилируемость выражения, опять же, и в зависимости от результата делать осмысленные вещи. EP>>В каком виде? В смысле только SFINAE-like или, допустим, можно поймать static_assert где-то в глубине call-stack'а?
DM>Можно в любом месте кода написать что-нибудь подобное: DM>
DM>static if (__traits(compiles, x + 1))
DM> writeln("number");
DM>else
DM> writeln("owl");
DM>
DM>Где вместо "х + 1" может быть любое выражение.
в С++11 есть SFINAE for expressions — практически то же самое, что __traits(compiles).
Здравствуйте, Evgeny.Panasyuk, Вы писали:
FR>>Массивы были в самой первой версии языка,
EP>То что они были это понятно, речь о том что их адаптировали к range.
В языке не адаптировали, в языке только массивы и срезы, вся адаптация для range в
std::array похоже я тебя просто недопонял и говорил именно про операцию индексации и
среза. Вот пример:
import std.stdio;
import std.array;
int count(Range)(Range r)
{
int result = 0;
for(; !r.empty; r.popFront())
++result;
return result;
}
void main()
{
writeln(count([1, 2, 3]));
}
если закомментировать import std.array; то будет такая ошибка компиляции:
count000.d(7): Error: no property 'empty' for type 'int[]'
count000.d(7): Error: no property 'popFront' for type 'int[]'
count000.d(14): Error: template instance count000.count!(int[]) error instantiating
FR>>в то время когда у Алекандреску с его rangeманией была совсем другая любимая игрушка
EP>Какая?
Ну "вот и выросло поколение" С++ конечно, как раз наверно писал свое "Современное проектирование на C++".
FR>>Думаю, пока как выкрутится
EP>Пока думаешь, можешь любоваться на: EP>
EP>template<typename I, typename T>
EP>I find(I first, I last, const T& x)
EP>{
EP> while(first != last && *first != x)
EP> ++first;
EP> return first;
EP>}
EP>
Если не нужна голова с рангами также:
Range find(Range, T)(Range r, T t)
{
while(!r.empty && r.front != t)
r.popFront();
return r;
}
А вообще я уже хотел идти искать белый флаг и приготовился начать писать
итераторы на D, но что-то нашептывало что где-то ошибка в рассуждениях.
В общем все просто мы тут пытаемся скопом сравнивать любые итераторы и так как-будто
для любых из них все плюсы имеют место.
А надо все-таки смотреть на их типы.
Берем простейший input iterator и соответственно input range. Для них описываемый
тобой плюс в виде легкого бесплатного одновременного получения головы и хвоста не
верен, голову мы получить не можем, поток всегда проходится только раз.
Идем дальше forward iterator и forward range. Тут мы всегда можем комбинировать
пару итераторов и легко получить и голову и хвост. Но это же означает что мы всегда
можем дешево сохранять состояние forward range, и действительно в D это обязательное
требование (и такой интервал должен иметь член save), следовательно все возражения о дорогих
копиях интервалов и их дорогих прокрутках отметаются, все очень дешево.
Для bidirectional справедливо все тоже что и для forward.
Для random_access все вообще полностью бесплатно, доступны длина и индекс.
В сухом остатке получается что единственно некоторые алгоритмы запишутся с интервалами
менее красиво чем с итераторами, но тут рулит то что интервалы очень легко комбинируются в
отличии от итераторов и на примере того же find выше
Здравствуйте, jazzer, Вы писали:
J>Концепции — это не только проверки, это еще и type classes из Haskell. Плюс соответствующие перегрузки/специализации. Ты не офигеешь все это явно кодить на D-шном интерпретаторе?
А чего там кодить?
void test(R)(R xs) if (isRandomAccessRange!R)
{...}
void test(R)(R xs) if (isForwardRange!R)
{...}
Тайпклассы, перегрузки, вот они.
J>в С++11 есть SFINAE for expressions — практически то же самое, что __traits(compiles).
Но только снаружи функции? Посреди метода я могу сделать несколько таких вложенных проверок?
Здравствуйте, matumba, Вы писали:
M>Вся надежда на D+LLVM.
И тебя не смущает, что LLVM на С++ написана? Как же можно на "говнокод на говноязыке" полагаться? Нее, надо срочно переписывать на D, а до этого ни за какие проекты браться ни в коем случае нельзя.
Здравствуйте, D. Mon, Вы писали:
DM>Здравствуйте, jazzer, Вы писали:
J>>Концепции — это не только проверки, это еще и type classes из Haskell. Плюс соответствующие перегрузки/специализации. Ты не офигеешь все это явно кодить на D-шном интерпретаторе?
DM>А чего там кодить? DM>
DM>void test(R)(R xs) if (isRandomAccessRange!R)
DM>{...}
DM>void test(R)(R xs) if (isForwardRange!R)
DM>{...}
DM>
DM>Тайпклассы, перегрузки, вот они.
а тут не будет неоднозначности?
Ну и тайпклассы еще осуществляют маппинг типов на концепции. Типа если у тебя концепция требует, чтоб у класса был size(), а у него вместо этого length(), то тайпклассы и концепции позволяют указать, что для этого класса надо звать length().
J>>в С++11 есть SFINAE for expressions — практически то же самое, что __traits(compiles).
DM>Но только снаружи функции? Посреди метода я могу сделать несколько таких вложенных проверок?
Здравствуйте, jazzer, Вы писали:
DM>>Тайпклассы, перегрузки, вот они. J>а тут не будет неоднозначности?
Может быть. Сейчас освежил в памяти детали. Короче, сначала констрейнты отсекают неподходящие варианты, а потом специализация выбирает самый подходящий. Если все еще есть неоднозначность, выдается ошибка. Поэтому на практике популярен подход со static if, там в явном виде логика выбора нужного варианта задается.
Здравствуйте, D. Mon, Вы писали:
EP>>Пока думаешь, можешь любоваться на EP>>
EP>>template<typename I, typename T>
EP>>I find(I first, I last, const T& x)
EP>>{
EP>> while(first != last && *first != x)
EP>> ++first;
EP>> return first;
EP>>}
EP>>
DM>Свой тип не нужен, нужные типы генерятся сами при использовании комбинаторов из стандартной библиотеки. DM>
DM>auto firstPart(R,T)(R xs, T x)
DM>{
DM> auto n = xs.save.countUntil(x);
DM> if (n < 0) return xs;
DM> return xs.takeExactly(n);
DM>}
DM>
DM>Те же три строчки, та же сложность, меньше мест для ошибки. DM>(и нет, тут нет двухкратного прохода)
1. Если звать стандартную библиотеку на помощь, то почему бы не взять сразу Until Речь то идёт об реализации. Код find выше — полностью self-contained.
2. find работает и для single pass range, а твой .save есть только начиная с forward — так?
3. Во время поиска у тебя передёргиваются и popFront и n, в find только итератор — оверхед по вычислениям
4. У тебя в результате будет и range и n, а в find только один итератор — то есть получается оверхед по памяти.
5. Это n, по сути является разжалованным итератором. Точно также, как я показывал выше, в Phobos'е, для алгоритмов по RandomAccess используются обычные целые, которые не знают откуда они пришли, и по сути те же итераторы, только менее удобные и безопасные.
6. В STL есть так называемые Counted Range — они определяются итератором и количеством элементов. Ты как раз попытался изобразить этот CountedRange, только менее эффективно.
7. find даёт два range'а, бесплатно, а у тебя только один. Например:
auto it = find(first, last, x);
process_first_half(first, it);
process_second_half(it, last);
8. У тебя возвращается либо один тип range (исходный), либо другой (то что вернёт takeExactly) — какой в итоге тип у твоего firstPart?
Здравствуйте, FR, Вы писали:
FR>если закомментировать import std.array; то будет такая ошибка компиляции: FR>
FR>count000.d(7): Error: no property 'empty' for type 'int[]'
FR>count000.d(7): Error: no property 'popFront' for type 'int[]'
FR>count000.d(14): Error: template instance count000.count!(int[]) error instantiating
FR>
ок, спасибо за информацию.
FR>>>в то время когда у Алекандреску с его rangeманией была совсем другая любимая игрушка EP>>Какая? FR>Ну "вот и выросло поколение" С++ конечно, как раз наверно писал свое "Современное проектирование на C++".
Я думал ты про D А эту книжку он лет за 8 перед range'ами написал.
FR>Если не нужна голова с рангами также: FR>
Как не нужна — конечно нужна, A.A. ведь как раз про голову и говорил.
FR>А вообще я уже хотел идти искать белый флаг и приготовился начать писать итераторы на D,
Хорошая идея
FR>но что-то нашептывало что где-то ошибка в рассуждениях.
Гони эти мысли, нет тут ошибки
FR>В общем все просто мы тут пытаемся скопом сравнивать любые итераторы и так как-будто FR>для любых из них все плюсы имеют место. FR>А надо все-таки смотреть на их типы.
FR>Берем простейший input iterator и соответственно input range. Для них описываемый FR>тобой плюс в виде легкого бесплатного одновременного получения головы и хвоста не FR>верен, голову мы получить не можем, поток всегда проходится только раз.
Я как раз выше и говорил, что D ranges для single pass ещё ничего:
EP>По факту D Range нормально подходит только для SinglePass.
FR>Идем дальше forward iterator и forward range. Тут мы всегда можем комбинировать FR>пару итераторов и легко получить и голову и хвост. Но это же означает что мы всегда FR>можем дешево сохранять состояние forward range, и действительно в D это обязательное FR>требование (и такой интервал должен иметь член save), следовательно все возражения о дорогих FR>копиях интервалов и их дорогих прокрутках отметаются, все очень дешево. FR>Для bidirectional справедливо все тоже что и для forward. FR>Для random_access все вообще полностью бесплатно, доступны длина и индекс.
Какие копии? Напиши find, который работает для всего начиная с single pass, а для forward и выше позволяет бесплатно получить первую часть в придачу ко второй
Лишние прокрутки были и есть в bidirectional partition, цена этих лишних прокруток зависит от используемой реализации range. Где-то передёргивание может быть дорогим, где-то дешёвым. В версии с итераторами, этих прокруток нет, by design
FR>В сухом остатке получается что единственно некоторые алгоритмы запишутся с интервалами FR>менее красиво чем с итераторами, но тут рулит то что интервалы очень легко комбинируются в FR>отличии от итераторов
Я же не против range'ей в стиле Boost.Range — они действительно легко комбинируются:
Но — из таких range всегда можно достать итераторы, и работать с ними, а вот из D-style range — нет.
То есть гибридное решение — итераторы + сахар в виде range вокруг них, вполне жизнеспособное.
FR>и на примере того же find выше
Здравствуйте, D. Mon, Вы писали:
DM>>>Но не только это, а выполнение почти произвольного кода. Прочитать файл, отсортировать, распарсить, посчитать... EP>>Это действительно круто(так же круто как и mixins), но эта фича слабо связанна с концепциям DM>Потому что концепции — слабое решение более общей проблемы — компайл-тайм проверки входных параметров на нужные свойства и выбор действий в зависимости от этого. Так вот, подобные проверки в D всегда будут мощнее плюсовых за счет встроенного интерпретатора.
Более мощные проверки — это всё понятно, и действительно мощно. Но это всё не то, и скорее относятся к compile-time вычислениям в общем, чем к концепциям.
Концепции выберут нужную перегрузку, а static if из твоего примера:
void test(R)(R xs) if (isRandomAccessRange!R)
{...}
void test(R)(R xs) if (isForwardRange!R)
{...}
сфейлится
DM>>>Проверить компилируемость выражения, опять же, и в зависимости от результата делать осмысленные вещи. EP>>В каком виде? В смысле только SFINAE-like или, допустим, можно поймать static_assert где-то в глубине call-stack'а? DM>Можно в любом месте кода написать что-нибудь подобное: DM>
DM>static if (__traits(compiles, x + 1))
DM> writeln("number");
DM>else
DM> writeln("owl");
DM>
DM>Где вместо "х + 1" может быть любое выражение.
Это как раз SFINAE-like.
А мне интересно, можно ли поймать такое:
static if (__traits(compiles, some_function_with_static_assert_inside() ))
// ..
EP>>4. Какой тест позволит различить InputIterator и ForwardIterator? DM>В стандартной либе есть предикаты вроде isForwardRange, внутри там обычный hasMember, т.к. подобные штуки имеют разные наборы функций.
В STL их тоже можно различить, но речь не об этом, а о том, что далеко не все свойства можно протестировать.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>1. Если звать стандартную библиотеку на помощь, то почему бы не взять сразу Until Речь то идёт об реализации. Код find выше — полностью self-contained.
В библиотеке уже есть findSplit, findSplitAfter и findSplitBefore, первый как раз дает два рэнджа, два других — левую и правую часть. Но они ищут не один элемент, а сразу "подстроку", поэтому их приводить я не стал.
EP>2. find работает и для single pass range, а твой .save есть только начиная с forward — так?
Так. Возврат левой части (от начала до найденного элемента) для не forward рэнджа смысла не имеет, элементы уже однажды прочитаны. D такую попытку здесь пресекает. А плюсы? Спокойно вернут итератор на протухшие данные?
EP>3. Во время поиска у тебя передёргиваются и popFront и n, в find только итератор — оверхед по вычислениям
Ага, на один инкремент целого. Что на порядок быстрее, чем чтение байта из ближайшего кэша даже. Т.е. реально не оверхэд абсолютно, спасибо instruction level parallelism.
EP>4. У тебя в результате будет и range и n, а в find только один итератор — то есть получается оверхед по памяти.
Да, опять же на целое одно число. Мы собираемся зачем-то хранить мильоны итераторов/рэнджей? На практике их обычно в один момент до нескольких штук используется, и те на стеке.
EP>5. Это n, по сути является разжалованным итератором. Точно также, как я показывал выше, в Phobos'е, для алгоритмов по RandomAccess используются обычные целые, которые не знают откуда они пришли, и по сути те же итераторы, только менее удобные и безопасные.
А какая есть безопасная альтернатива для Random access?
EP>6. В STL есть так называемые Counted Range — они определяются итератором и количеством элементов. Ты как раз попытался изобразить этот CountedRange, только менее эффективно.
Почему менее?
EP>7. find даёт два range'а, бесплатно, а у тебя только один.
Мне только он и нужен был. Вернуть вместо xs tuple(xs, ys) (где ys — сохраненный двумя строчками выше xs.save) много ума не нужно.
EP>8. У тебя возвращается либо один тип range (исходный), либо другой (то что вернёт takeExactly) — какой в итоге тип у твоего firstPart?
Хороший вопрос. Почему-то компилятор в нем разобрался без моей помощи.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
DM>>static if (__traits(compiles, x + 1)) DM>>Где вместо "х + 1" может быть любое выражение.
EP>Это как раз SFINAE-like.
Я не вижу сходства. Выше уже сказали, что SFINAE нельзя использовать внутри функций, в отличие от static if.
EP>А мне интересно, можно ли поймать такое: EP>
EP>static if (__traits(compiles, some_function_with_static_assert_inside() ))
EP>// ..
EP>
Да, может. Хоть static assert, хоть динамическая проверка.
Такой пример только что запустил:
EP>>1. Если звать стандартную библиотеку на помощь, то почему бы не взять сразу Until Речь то идёт об реализации. Код find выше — полностью self-contained.
DM>В библиотеке уже есть findSplit, findSplitAfter и findSplitBefore, первый как раз дает два рэнджа, два других — левую и правую часть.
findSplit вообще возвращает три range, а нужны [first, find(first, last)) и [find(first, last), last), или например тоже самое с partition.
EP>>2. find работает и для single pass range, а твой .save есть только начиная с forward — так? DM>Так. Возврат левой части (от начала до найденного элемента) для не forward рэнджа смысла не имеет, элементы уже однажды прочитаны. D такую попытку здесь пресекает. А плюсы? Спокойно вернут итератор на протухшие данные?
Покажи реализацию аналога std::find.
EP>>3. Во время поиска у тебя передёргиваются и popFront и n, в find только итератор — оверхед по вычислениям DM>Ага, на один инкремент целого. Что на порядок быстрее, чем чтение байта из ближайшего кэша даже. Т.е. реально не оверхэд абсолютно, спасибо instruction level parallelism.
Не один инкремент, а на каждой итерации. И не instruction level parallelism, а overhead by design, чего тут спорить
EP>>4. У тебя в результате будет и range и n, а в find только один итератор — то есть получается оверхед по памяти. DM>Да, опять же на целое одно число. Мы собираемся зачем-то хранить мильоны итераторов/рэнджей? На практике их обычно в один момент до нескольких штук используется, и те на стеке.
Тут согласен — в большинстве случаев будет не заметно. Я лишь показываю насколько абстракция неудобная, даже для простых случаев
EP>>5. Это n, по сути является разжалованным итератором. Точно также, как я показывал выше, в Phobos'е, для алгоритмов по RandomAccess используются обычные целые, которые не знают откуда они пришли, и по сути те же итераторы, только менее удобные и безопасные. DM>А какая есть безопасная альтернатива для Random access?
В смысле? Вот ты реализуешь некий алгоритм который использует несколько random access range'ей — дёргаешь индексы туда-сюда, при использовании D Range нужно ещё не перепутать из какого range какой индекс, в том время как из самого итератора можно и читать/писать.
EP>>6. В STL есть так называемые Counted Range — они определяются итератором и количеством элементов. Ты как раз попытался изобразить этот CountedRange, только менее эффективно. DM>Почему менее?
Лишние операции, лишняя память.
EP>>7. find даёт два range'а, бесплатно, а у тебя только один. DM>Мне только он и нужен был. Вернуть вместо xs tuple(xs, ys) (где ys — сохраненный двумя строчками выше xs.save) много ума не нужно.
Ну так покажи полную реализацию аналога find, если всё так просто
EP>>8. У тебя возвращается либо один тип range (исходный), либо другой (то что вернёт takeExactly) — какой в итоге тип у твоего firstPart? DM>Хороший вопрос. Почему-то компилятор в нем разобрался без моей помощи.