Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В качестве наглядного примера можно взять итераторы в C# или даже более продвинутую реализацию. Неважно.
Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
Попробую объяснить, что я имею в виду. Положим, некий код вида:
IEnumerable<Int32> Range(int start, int end)
{
var e = end + 1;
for (var i = start; i < e; i++)
yield return i;
}
foreach (var i in Range(0, 100)) {
//Do something
}
Можно переписать вот так:
void Range(int start, int end, Action<Int32> fun)
{
var e = end + 1;
for (var i = start; i < e; i++)
fun(i);
}
Можно представить так и работу с бесконечными последовательностями, если действие будет описывать как Func<T,Int32,Boolean>, где второй параметр — порядковый номер элемента, а первый сам элемент. Возвращается же флажок, по которому определяются следует ли нам продолжать. Создавать такую функцию можно через простейший комбинатор вида:
Func<T,Int32,Boolean> Create(Action<T> fun, int take)
{
var t = take + 1;
return (e, i) => {
if (i < t) {
fun(e);
return true;
}
else
return false;
};
}
Т.е. с точки зрения юзабилити это, конечно, менее удобно, но вот, собственно, и все.
В принципе у итераторов как частного случая корутин должны быть более широкие применения вроде как, но все, о чем я могу подумать, прекрасно выражается через ФВП. Причем вариант с ФВП отлично дружит с continuation-ами, с которыми те же C#-вые итераторы не дружат совсем.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В качестве наглядного примера можно взять итераторы в C# или даже более продвинутую реализацию. Неважно.
ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
ВВ>Попробую объяснить, что я имею в виду. Положим, некий код вида:
ВВ>
ВВ>IEnumerable<Int32> Range(int start, int end)
ВВ>{
ВВ> var e = end + 1;
ВВ> for (var i = start; i < e; i++)
ВВ> yield return i;
ВВ>}
ВВ>foreach (var i in Range(0, 100)) {
ВВ> //Do something
ВВ>}
ВВ>
ВВ>Можно переписать вот так:
ВВ>
ВВ>void Range(int start, int end, Action<Int32> fun)
ВВ>{
ВВ> var e = end + 1;
ВВ> for (var i = start; i < e; i++)
ВВ> fun(i);
ВВ>}
ВВ>
ВВ>Можно представить так и работу с бесконечными последовательностями, если действие будет описывать как Func<T,Int32,Boolean>, где второй параметр — порядковый номер элемента, а первый сам элемент. Возвращается же флажок, по которому определяются следует ли нам продолжать. Создавать такую функцию можно через простейший комбинатор вида:
ВВ>
ВВ>Func<T,Int32,Boolean> Create(Action<T> fun, int take)
ВВ>{
ВВ> var t = take + 1;
ВВ> return (e, i) => {
ВВ> if (i < t) {
ВВ> fun(e);
ВВ> return true;
ВВ> }
ВВ> else
ВВ> return false;
ВВ> };
ВВ>}
ВВ>
ВВ>Т.е. с точки зрения юзабилити это, конечно, менее удобно, но вот, собственно, и все.
ВВ>В принципе у итераторов как частного случая корутин должны быть более широкие применения вроде как, но все, о чем я могу подумать, прекрасно выражается через ФВП. Причем вариант с ФВП отлично дружит с continuation-ами, с которыми те же C#-вые итераторы не дружат совсем.
ВВ>Т.е. итераторы просто сахар? Зачем они нужны?
Ну как-бы исторический фактор. Сначала были сделаны IEnumerator\IEnumerable и сахар в виде foreach для них. Потом разработчики компиляторы подумали что писать руками итераторы слишком сложно и сделали сахар в виде yield return для них.
Кроме того continuations в императивном стиле очень тяжело писать (без сахара вроде async\await).
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В качестве наглядного примера можно взять итераторы в C# или даже более продвинутую реализацию. Неважно.
ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
ВВ>Т.е. итераторы просто сахар? Зачем они нужны?
Поздравляю с открытием CPS Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.
Здравствуйте, gandjustas, Вы писали:
G>Ну как-бы исторический фактор. Сначала были сделаны IEnumerator\IEnumerable и сахар в виде foreach для них. Потом разработчики компиляторы подумали что писать руками итераторы слишком сложно и сделали сахар в виде yield return для них. G>Кроме того continuations в императивном стиле очень тяжело писать (без сахара вроде async\await).
Этот вопрос меня интересует вообще применительно к другому языку, я "перевожу стрелки" на C#, чтобы было проще обсуждать. Т.е. тяжело, неудобно писать — это все понятно. Интересно, дают ли что-либо итераторы, кроме "сахарности"?
Здравствуйте, lomeo, Вы писали:
ВВ>>Т.е. итераторы просто сахар? Зачем они нужны? L>Мне кажется, ты перевёл всё в CPS. Представь как будет выглядеть цепочка функций без итераторов.
На C# — не очень. На каком-нибудь более другом языке — вполне нормально. Скажем, альтернативой линковского Where(...).Select(...) будет композиция функций. Насколько это будет выглядеть лучше — вопрос относительный. Можно вообще сделать специальный сахар для создания каких-нибудь узко-специализированных композиций, которые будут покрывать такие сценарии.
У меня возникает вообще впечатление, что итераторы сами по себе мешают писать функциональный код. С рекурсией у них как-то тухло. Сами по себе они выступают в виде этакой альтернативы более привычным вещам, вроде созданию ФВП, композиции селекторов. Или может, есть какие-то более правильные итераторы, о которых я не знаю?
Вообще вопрос можно задать так — почему итераторы не нужны в Хаскеле?
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
ВВ> for (var i = start; i < e; i++) ВВ> fun(i);
Тебя, видимо, слишком уводит в сторону тут специфика C#. "В общем и целом" переменная i в данном случае — итератор. У тебя в примере он — целое число, но представь себе что-то более сложное: например, перебор элементов дерева, или ответа на SQL запрос. В этом случае ты уже не сможешь написать такой for в простом виде — и вынужден будешь или писать с учётом внутренностей реализации, или таки делать итератор. А будет этот итератор сделан через yield, через объект с методом next() или как-то ещё — уже неважно.
Здравствуйте, k.o., Вы писали:
ВВ>>Т.е. итераторы просто сахар? Зачем они нужны? KO>Поздравляю с открытием CPS
Мне кажется, его уже открыли до меня
KO>Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.
Если вопрос *только* в удобстве, почему в C# итераторы присутствуют как отдельная фича, да и компилятор там немало кода генерит? Достаточно было бы просто ввести некий сахар "для удобства" и все. Тогда бы у нас была фича, реализованная по классической схеме:
Здравствуйте, netch80, Вы писали:
N>Тебя, видимо, слишком уводит в сторону тут специфика C#. "В общем и целом" переменная i в данном случае — итератор. У тебя в примере он — целое число, но представь себе что-то более сложное: например, перебор элементов дерева, или ответа на SQL запрос. В этом случае ты уже не сможешь написать такой for в простом виде — и вынужден будешь или писать с учётом внутренностей реализации, или таки делать итератор. А будет этот итератор сделан через yield, через объект с методом next() или как-то ещё — уже неважно.
Я вообще спрашиваю о генераторах, если такой термин понятнее/привычнее. В дотнете IEnumerable выполняет две задачи — абстракция для перебора элементов (тут ОК, и эта тема вообще отдельная) и интерфейс функции-генератора (т.е. функции, которая умеет возвращать свое значение несколько раз и сохраняет весь свой стек между вызовами).
Итератор (курсор) — идиома ООП.
И вопрос, наверное, следовало бы поставить как: "Почему нужны итераторы" — и вот тут есть довольно резонный ответ — потому что, как правило, ООП-языки не держат функцию за первоклассный объект. Приходится выдумывать в рамках ООП костылёк. Я совершенно не удивлюсь, если мне скажут, что Visitor не нужен, потому что есть map()
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Здравствуйте, k.o., Вы писали:
ВВ>>>Т.е. итераторы просто сахар? Зачем они нужны? KO>>Поздравляю с открытием CPS
ВВ>Мне кажется, его уже открыли до меня
KO>>Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.
ВВ>Если вопрос *только* в удобстве, почему в C# итераторы присутствуют как отдельная фича, да и компилятор там немало кода генерит? Достаточно было бы просто ввести некий сахар "для удобства" и все. Тогда бы у нас была фича, реализованная по классической схеме:
ВВ>- Стандартный паттерн ВВ>- Сахарок сверху, повышающий удобство
ВВ>Что, неужели итераторы в том виде, в котором они есть, появились просто по "историческим причинам"?
А что такое порядковый номер? Есть масса контейнеров, где он невозможен или бессмысленен. А так можно писать алгоритм, который слабо зависит от контейнера
, Вы писали:
ВВ>Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В качестве наглядного примера можно взять итераторы в C# или даже более продвинутую реализацию. Неважно.
ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
ВВ>Попробую объяснить, что я имею в виду. Положим, некий код вида:
ВВ>
ВВ>IEnumerable<Int32> Range(int start, int end)
ВВ>{
ВВ> var e = end + 1;
ВВ> for (var i = start; i < e; i++)
ВВ> yield return i;
ВВ>}
ВВ>foreach (var i in Range(0, 100)) {
ВВ> //Do something
ВВ>}
ВВ>
ВВ>Можно переписать вот так:
ВВ>
ВВ>void Range(int start, int end, Action<Int32> fun)
ВВ>{
ВВ> var e = end + 1;
ВВ> for (var i = start; i < e; i++)
ВВ> fun(i);
ВВ>}
ВВ>
ВВ>Можно представить так и работу с бесконечными последовательностями, если действие будет описывать как Func<T,Int32,Boolean>, где второй параметр — порядковый номер элемента, а первый сам элемент. Возвращается же флажок, по которому определяются следует ли нам продолжать. Создавать такую функцию можно через простейший комбинатор вида:
ВВ>
ВВ>Func<T,Int32,Boolean> Create(Action<T> fun, int take)
ВВ>{
ВВ> var t = take + 1;
ВВ> return (e, i) => {
ВВ> if (i < t) {
ВВ> fun(e);
ВВ> return true;
ВВ> }
ВВ> else
ВВ> return false;
ВВ> };
ВВ>}
ВВ>
ВВ>Т.е. с точки зрения юзабилити это, конечно, менее удобно, но вот, собственно, и все.
ВВ>В принципе у итераторов как частного случая корутин должны быть более широкие применения вроде как, но все, о чем я могу подумать, прекрасно выражается через ФВП. Причем вариант с ФВП отлично дружит с continuation-ами, с которыми те же C#-вые итераторы не дружат совсем.
ВВ>Т.е. итераторы просто сахар? Зачем они нужны?
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Здравствуйте, netch80, Вы писали:
N>>Тебя, видимо, слишком уводит в сторону тут специфика C#. "В общем и целом" переменная i в данном случае — итератор. У тебя в примере он — целое число, но представь себе что-то более сложное: например, перебор элементов дерева, или ответа на SQL запрос. В этом случае ты уже не сможешь написать такой for в простом виде — и вынужден будешь или писать с учётом внутренностей реализации, или таки делать итератор. А будет этот итератор сделан через yield, через объект с методом next() или как-то ещё — уже неважно.
ВВ>Я вообще спрашиваю о генераторах, если такой термин понятнее/привычнее. В дотнете IEnumerable выполняет две задачи — абстракция для перебора элементов (тут ОК, и эта тема вообще отдельная) и интерфейс функции-генератора (т.е. функции, которая умеет возвращать свое значение несколько раз и сохраняет весь свой стек между вызовами).
Так тебя интересует полезность конструкции генератора именно в виде функции с yield? Если так — то вопрос сводится к тому, что другой дизайн требует реализации (хоть обычно и крошечной, но) FSM, а это для многих уже высший пилотаж.
Да, тогда можно форму с yield считать сахаром.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Здравствуйте, k.o., Вы писали:
ВВ>>>Т.е. итераторы просто сахар? Зачем они нужны? KO>>Поздравляю с открытием CPS
ВВ>Мне кажется, его уже открыли до меня
KO>>Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.
ВВ>Если вопрос *только* в удобстве, почему в C# итераторы присутствуют как отдельная фича, да и компилятор там немало кода генерит? Достаточно было бы просто ввести некий сахар "для удобства" и все. Тогда бы у нас была фича, реализованная по классической схеме:
ВВ>- Стандартный паттерн ВВ>- Сахарок сверху, повышающий удобство
ВВ>Что, неужели итераторы в том виде, в котором они есть, появились просто по "историческим причинам"?
Вобще, ИМХО, очевидно, что итераторы, в том виде, как они есть сейчас, можно безболезненно прикрутить к большинству .NET языков (если бы их там не было). При этом использование CPS потребовало бы совершенно другой реализации и для C# и для VB.NET и для виртуальной машины, причём есть сомнения в том, что эта реализация могла бы быть не менее эффективна.
Здравствуйте, Lloyd, Вы писали:
ВВ>>Вообще вопрос можно задать так — почему итераторы не нужны в Хаскеле? L>Потому что он весь из себя ленивый — ты свободно можешь создать бесконечный список и вернуть его из фнкции.
То, что я могу сделать *на самом деле* — это через итератор выразить некую non-strict последовательность *вычислений*. Это же я могу сделать и без итераторов через CPS. А для того, чтобы создать а) *список* и б) ленивый список с мемоизацией — мне и с итераторами придется повозиться. Т.е. итератор мне никак не заменит, скажем, такую функцию, которая пишется на языке с поддержкой ленивости, но без всяких итераторов:
let where f x::xs = if f x thenx :: (lazy where f xs)else where f xs
| f [] = [];
Здравствуйте, PoM-PoM 40mm, Вы писали:
PP4>Здравствуйте, Воронков Василий PP4>А что такое порядковый номер? Есть масса контейнеров, где он невозможен или бессмысленен. А так можно писать алгоритм, который слабо зависит от контейнера
Например, как? Все способы, предлагаемые линком, чтобы работать с циклическим итератором вида
for (;;) yield 1;
это именно "отщепить" кусочек. Причем кусочек "в цифрах". Будь то First или Take. Вы можете еще раскрутить его через foreach, а в нужный момент дернуть break — но для всего, что можно перебрать через foreach порядковый номер элемента вполне имеет смысл хотя бы рамках этого перебора.
Т.е. линк со своими итераторами и сам никаких особых техник работы с бесконечными последовательностями не предлагает.
Здравствуйте, netch80, Вы писали:
N>Так тебя интересует полезность конструкции генератора именно в виде функции с yield? Если так — то вопрос сводится к тому, что другой дизайн требует реализации (хоть обычно и крошечной, но) FSM, а это для многих уже высший пилотаж. N>Да, тогда можно форму с yield считать сахаром.
FSM не нужен, если есть первоклассные функции. А yield — это может и сахар, но никак не сахар для ФВП. Т.е. то, что дает yield, реализуется через ФВП, но сам yield сделан иначе. Мне интересно — почему.
ОК, спасибо, посмотрю.
L>Что касается вопроса — ответили уже про ленивость.
А что с ленивостью? То, что дает итератор в этом плане, прекрасно реализуется опять же без итератора через ФВП.
Кстати, если захочется, скажем, реализовать на C# свой связный список и сделать его ленивым с мемоизацией через итератор, то окажется, что стандартный итератор в этой задаче не поможет вообще никак и придется все делать ручками.