Интересные обсуждения

темы заинтересовавшие velkin

Необходимость итераторов в программировании

Воронков Василий Воронков Василий
Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В качестве наглядного примера можно взять итераторы в 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#-вые итераторы не дружат совсем.

Т.е. итераторы просто сахар? Зачем они нужны?
lomeo
lomeo Зачем нужны итераторы?
24.11.2010 09:58
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Т.е. итераторы просто сахар? Зачем они нужны?


Мне кажется, ты перевёл всё в CPS. Представь как будет выглядеть цепочка функций без итераторов.
Воронков Василий
Здравствуйте, lomeo, Вы писали:

ВВ>>Т.е. итераторы просто сахар? Зачем они нужны?

L>Мне кажется, ты перевёл всё в CPS. Представь как будет выглядеть цепочка функций без итераторов.

На C# — не очень. На каком-нибудь более другом языке — вполне нормально. Скажем, альтернативой линковского Where(...).Select(...) будет композиция функций. Насколько это будет выглядеть лучше — вопрос относительный. Можно вообще сделать специальный сахар для создания каких-нибудь узко-специализированных композиций, которые будут покрывать такие сценарии.

У меня возникает вообще впечатление, что итераторы сами по себе мешают писать функциональный код. С рекурсией у них как-то тухло. Сами по себе они выступают в виде этакой альтернативы более привычным вещам, вроде созданию ФВП, композиции селекторов. Или может, есть какие-то более правильные итераторы, о которых я не знаю?

Вообще вопрос можно задать так — почему итераторы не нужны в Хаскеле?
Lloyd
Lloyd
24.11.2010 10:32
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Вообще вопрос можно задать так — почему итераторы не нужны в Хаскеле?


Потому что он весь из себя ленивый — ты свободно можешь создать бесконечный список и вернуть его из фнкции.
Воронков Василий
Здравствуйте, Lloyd, Вы писали:

ВВ>>Вообще вопрос можно задать так — почему итераторы не нужны в Хаскеле?

L>Потому что он весь из себя ленивый — ты свободно можешь создать бесконечный список и вернуть его из фнкции.

То, что я могу сделать *на самом деле* — это через итератор выразить некую non-strict последовательность *вычислений*. Это же я могу сделать и без итераторов через CPS. А для того, чтобы создать а) *список* и б) ленивый список с мемоизацией — мне и с итераторами придется повозиться. Т.е. итератор мне никак не заменит, скажем, такую функцию, которая пишется на языке с поддержкой ленивости, но без всяких итераторов:

let where f x::xs = if f x then x :: (lazy where f xs) else where f xs
        | f []    = [];
lomeo
lomeo
24.11.2010 10:47
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Вообще вопрос можно задать так — почему итераторы не нужны в Хаскеле?


Немного не в тему. А посмотри на iteratee, если ещё не видел.
http://okmij.org/ftp/Haskell/Iteratee/DEFUN08-talk-notes.pdf

Что касается вопроса — ответили уже про ленивость.
Воронков Василий
Здравствуйте, lomeo, Вы писали:

L>Немного не в тему. А посмотри на iteratee, если ещё не видел.

L>http://okmij.org/ftp/Haskell/Iteratee/DEFUN08-talk-notes.pdf

ОК, спасибо, посмотрю.

L>Что касается вопроса — ответили уже про ленивость.


А что с ленивостью? То, что дает итератор в этом плане, прекрасно реализуется опять же без итератора через ФВП.
Кстати, если захочется, скажем, реализовать на C# свой связный список и сделать его ленивым с мемоизацией через итератор, то окажется, что стандартный итератор в этой задаче не поможет вообще никак и придется все делать ручками.
lomeo
lomeo
24.11.2010 11:42
Здравствуйте, Воронков Василий, Вы писали:

ВВ>А что с ленивостью? То, что дает итератор в этом плане, прекрасно реализуется опять же без итератора через ФВП.


Вопрос в другую сторону был поставлен. Почему в Haskell не нужны итераторы. Ответ — потому что ленивость уже решает эти задачи. Почему нужны в C# — может потому что так удобнее?
Воронков Василий
Здравствуйте, lomeo, Вы писали:

L>Вопрос в другую сторону был поставлен. Почему в Haskell не нужны итераторы. Ответ — потому что ленивость уже решает эти задачи. Почему нужны в C# — может потому что так удобнее?


У меня вообще схожие впечатления. Ленивость, необязательно неявная, если требуется non strict поведение — как при создании контейнеров, так и при вычислениях. И стандартные паттерны ФП для всего остального.

Т.е. при таком раскладе сама по себе функция-генератор оказывается как бы невостребованной.
VladD2
VladD2
30.11.2010 07:40
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Т.е. при таком раскладе сама по себе функция-генератор оказывается как бы невостребованной.


В указанной ссылке как раз презентация описывающая, что аналог итератора куда лучше чем "хэндл-бэзед ИО".
VladD2
VladD2
30.11.2010 07:39
Здравствуйте, lomeo, Вы писали:

L>Вопрос в другую сторону был поставлен. Почему в Haskell не нужны итераторы. Ответ — потому что ленивость уже решает эти задачи.


Не совсем приемлемый ответ. Все же списки, даже если они будут ленивыми, остаются конкретными структурами данных, а не абстракциями. Итератор шарпа же — это абстракция. Мы можем реализовать итератор поверх любой коллекции или даже без оной (пример — все тот же Range()).

L>Почему нужны в C# — может потому что так удобнее?


+1
lomeo
lomeo
01.12.2010 08:36
Здравствуйте, VladD2, Вы писали:

L>>Вопрос в другую сторону был поставлен. Почему в Haskell не нужны итераторы. Ответ — потому что ленивость уже решает эти задачи.

VD>Не совсем приемлемый ответ. Все же списки, даже если они будут ленивыми, остаются конкретными структурами данных, а не абстракциями. Итератор шарпа же — это абстракция. Мы можем реализовать итератор поверх любой коллекции или даже без оной (пример — все тот же Range()).

Под ленивостью я понимал ленивость, а не ленивые списки. А под итераторами — функции с yield.
gandjustas
gandjustas Зачем нужны итераторы?
24.11.2010 09:58
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В качестве наглядного примера можно взять итераторы в 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).
Воронков Василий
Здравствуйте, gandjustas, Вы писали:

G>Ну как-бы исторический фактор. Сначала были сделаны IEnumerator\IEnumerable и сахар в виде foreach для них. Потом разработчики компиляторы подумали что писать руками итераторы слишком сложно и сделали сахар в виде yield return для них.

G>Кроме того continuations в императивном стиле очень тяжело писать (без сахара вроде async\await).

Этот вопрос меня интересует вообще применительно к другому языку, я "перевожу стрелки" на C#, чтобы было проще обсуждать. Т.е. тяжело, неудобно писать — это все понятно. Интересно, дают ли что-либо итераторы, кроме "сахарности"?
VladD2
VladD2
30.11.2010 07:43
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Этот вопрос меня интересует вообще применительно к другому языку, я "перевожу стрелки" на C#, чтобы было проще обсуждать. Т.е. тяжело, неудобно писать — это все понятно. Интересно, дают ли что-либо итераторы, кроме "сахарности"?


Если тебя интересует выражаются ли итераторы шарпа через замыкания (точнее даже будет сказать, через монады) и т.п., то таки да, выражаются. Как и наоборот. Только вот это не говорит, что итераторы шарпа чем-то плохи. Реализация на без ДКА вполне эффективна и проста.
k.o.
k.o. Зачем нужны итераторы?
24.11.2010 09:58
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В качестве наглядного примера можно взять итераторы в C# или даже более продвинутую реализацию. Неважно.


ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.


ВВ>Т.е. итераторы просто сахар? Зачем они нужны?


Поздравляю с открытием CPS Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.
Воронков Василий
Здравствуйте, k.o., Вы писали:

ВВ>>Т.е. итераторы просто сахар? Зачем они нужны?

KO>Поздравляю с открытием CPS

Мне кажется, его уже открыли до меня

KO>Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.


Если вопрос *только* в удобстве, почему в C# итераторы присутствуют как отдельная фича, да и компилятор там немало кода генерит? Достаточно было бы просто ввести некий сахар "для удобства" и все. Тогда бы у нас была фича, реализованная по классической схеме:

— Стандартный паттерн
— Сахарок сверху, повышающий удобство

Что, неужели итераторы в том виде, в котором они есть, появились просто по "историческим причинам"?
k.o.
k.o.
24.11.2010 10:30
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Здравствуйте, k.o., Вы писали:


ВВ>>>Т.е. итераторы просто сахар? Зачем они нужны?

KO>>Поздравляю с открытием CPS

ВВ>Мне кажется, его уже открыли до меня


KO>>Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.


ВВ>Если вопрос *только* в удобстве, почему в C# итераторы присутствуют как отдельная фича, да и компилятор там немало кода генерит? Достаточно было бы просто ввести некий сахар "для удобства" и все. Тогда бы у нас была фича, реализованная по классической схеме:


ВВ>- Стандартный паттерн

ВВ>- Сахарок сверху, повышающий удобство

ВВ>Что, неужели итераторы в том виде, в котором они есть, появились просто по "историческим причинам"?


здесь
k.o.
k.o.
24.11.2010 11:00
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Здравствуйте, k.o., Вы писали:


ВВ>>>Т.е. итераторы просто сахар? Зачем они нужны?

KO>>Поздравляю с открытием CPS

ВВ>Мне кажется, его уже открыли до меня


KO>>Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.


ВВ>Если вопрос *только* в удобстве, почему в C# итераторы присутствуют как отдельная фича, да и компилятор там немало кода генерит? Достаточно было бы просто ввести некий сахар "для удобства" и все. Тогда бы у нас была фича, реализованная по классической схеме:


ВВ>- Стандартный паттерн

ВВ>- Сахарок сверху, повышающий удобство

ВВ>Что, неужели итераторы в том виде, в котором они есть, появились просто по "историческим причинам"?


Вобще, ИМХО, очевидно, что итераторы, в том виде, как они есть сейчас, можно безболезненно прикрутить к большинству .NET языков (если бы их там не было). При этом использование CPS потребовало бы совершенно другой реализации и для C# и для VB.NET и для виртуальной машины, причём есть сомнения в том, что эта реализация могла бы быть не менее эффективна.
VladD2
VladD2
30.11.2010 07:54
Здравствуйте, Воронков Василий, Вы писали:

KO>>Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.


ВВ>Если вопрос *только* в удобстве, почему в C# итераторы присутствуют как отдельная фича,


Да потому что они еще и через ДКА выражаются, и еще несколькими видами. Реализовывать ДКА или CPS вручную — это не очень благодарное занятие. А в виде фичи языка все выглядит просто, императивно (последовательно) и результат получается эффективный.

ВВ>да и компилятор там немало кода генерит?


Компилятор генерирует столько сколько нужно. Реализация получается весьма эффективной. Не хуже чем при использовании CSP.

Кстати, не все такой подход избрали. В F# для аналога генераторов используются Computation Expressions, то есть по сути CPS. В C# 5 появится sync/await. Реализация будет опять же на основе ДКА (точнее даже на основе итераторов). В F# и Nemerle есть аналогичная фича реализованная на базе Computation Expressions. Однако реализация на базе ДКА будет ничем не хуже. Так что возможно в немреле и ее реализуем.

ВВ>Достаточно было бы просто ввести некий сахар "для удобства" и все. Тогда бы у нас была фича, реализованная по классической схеме:


ВВ>- Стандартный паттерн

ВВ>- Сахарок сверху, повышающий удобство

Ну, вот Computation Expressions и есть такой подход. Наверно и с итераторами и sync/await можно было так поступить. Но Хейльсберг все в язык тащит.

Но если тогда уж задуматься, то лучшим выходом было бы просто ввести макросы, а уже все подобные детали реализовать на их основе.
Воронков Василий
Здравствуйте, VladD2, Вы писали:

VD>Кстати, не все такой подход избрали. В F# для аналога генераторов используются Computation Expressions, то есть по сути CPS. В C# 5 появится sync/await. Реализация будет опять же на основе ДКА (точнее даже на основе итераторов). В F# и Nemerle есть аналогичная фича реализованная на базе Computation Expressions. Однако реализация на базе ДКА будет ничем не хуже. Так что возможно в немреле и ее реализуем.


В F# есть "yield!". Как он будет работать в варианте с ДКА?
VladD2
VladD2
01.12.2010 03:41
Здравствуйте, Воронков Василий, Вы писали:

ВВ>В F# есть "yield!". Как он будет работать в варианте с ДКА?


Описание того как sync/await сделаны в шарпе доступны в Интернете. Да поможет тебе гугль. Ну, а то что названия не совпадают, ты уже извини.
netch80
netch80 Зачем нужны итераторы?
24.11.2010 10:16
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.


ВВ> for (var i = start; i < e; i++)

ВВ> fun(i);

Тебя, видимо, слишком уводит в сторону тут специфика C#. "В общем и целом" переменная i в данном случае — итератор. У тебя в примере он — целое число, но представь себе что-то более сложное: например, перебор элементов дерева, или ответа на SQL запрос. В этом случае ты уже не сможешь написать такой for в простом виде — и вынужден будешь или писать с учётом внутренностей реализации, или таки делать итератор. А будет этот итератор сделан через yield, через объект с методом next() или как-то ещё — уже неважно.
Воронков Василий
Здравствуйте, netch80, Вы писали:

N>Тебя, видимо, слишком уводит в сторону тут специфика C#. "В общем и целом" переменная i в данном случае — итератор. У тебя в примере он — целое число, но представь себе что-то более сложное: например, перебор элементов дерева, или ответа на SQL запрос. В этом случае ты уже не сможешь написать такой for в простом виде — и вынужден будешь или писать с учётом внутренностей реализации, или таки делать итератор. А будет этот итератор сделан через yield, через объект с методом next() или как-то ещё — уже неважно.


Я вообще спрашиваю о генераторах, если такой термин понятнее/привычнее. В дотнете IEnumerable выполняет две задачи — абстракция для перебора элементов (тут ОК, и эта тема вообще отдельная) и интерфейс функции-генератора (т.е. функции, которая умеет возвращать свое значение несколько раз и сохраняет весь свой стек между вызовами).
netch80
netch80
24.11.2010 10:44
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Здравствуйте, netch80, Вы писали:


N>>Тебя, видимо, слишком уводит в сторону тут специфика C#. "В общем и целом" переменная i в данном случае — итератор. У тебя в примере он — целое число, но представь себе что-то более сложное: например, перебор элементов дерева, или ответа на SQL запрос. В этом случае ты уже не сможешь написать такой for в простом виде — и вынужден будешь или писать с учётом внутренностей реализации, или таки делать итератор. А будет этот итератор сделан через yield, через объект с методом next() или как-то ещё — уже неважно.


ВВ>Я вообще спрашиваю о генераторах, если такой термин понятнее/привычнее. В дотнете IEnumerable выполняет две задачи — абстракция для перебора элементов (тут ОК, и эта тема вообще отдельная) и интерфейс функции-генератора (т.е. функции, которая умеет возвращать свое значение несколько раз и сохраняет весь свой стек между вызовами).


Так тебя интересует полезность конструкции генератора именно в виде функции с yield? Если так — то вопрос сводится к тому, что другой дизайн требует реализации (хоть обычно и крошечной, но) FSM, а это для многих уже высший пилотаж.
Да, тогда можно форму с yield считать сахаром.
Воронков Василий
Здравствуйте, netch80, Вы писали:

N>Так тебя интересует полезность конструкции генератора именно в виде функции с yield? Если так — то вопрос сводится к тому, что другой дизайн требует реализации (хоть обычно и крошечной, но) FSM, а это для многих уже высший пилотаж.

N>Да, тогда можно форму с yield считать сахаром.

FSM не нужен, если есть первоклассные функции. А yield — это может и сахар, но никак не сахар для ФВП. Т.е. то, что дает yield, реализуется через ФВП, но сам yield сделан иначе. Мне интересно — почему.
Wolverrum
Wolverrum Зачем нужны итераторы?
24.11.2010 10:29
Итератор (курсор) — идиома ООП.
И вопрос, наверное, следовало бы поставить как: "Почему нужны итераторы" — и вот тут есть довольно резонный ответ — потому что, как правило, ООП-языки не держат функцию за первоклассный объект. Приходится выдумывать в рамках ООП костылёк. Я совершенно не удивлюсь, если мне скажут, что Visitor не нужен, потому что есть map()
VladD2
VladD2
30.11.2010 08:00
Здравствуйте, Wolverrum, Вы писали:

W>Итератор (курсор) — идиома ООП.

W>И вопрос, наверное, следовало бы поставить как: "Почему нужны итераторы" — и вот тут есть довольно резонный ответ — потому что, как правило, ООП-языки не держат функцию за первоклассный объект. Приходится выдумывать в рамках ООП костылёк. Я совершенно не удивлюсь, если мне скажут, что Visitor не нужен, потому что есть map()

А я не удивлюсь если тебе замеят, что шаблон Посетитель и функция map решают разные задачи.
Wolverrum
Wolverrum
02.12.2010 01:36
Здравствуйте, VladD2, Вы писали:

VD>А я не удивлюсь если тебе замеят, что шаблон Посетитель и функция map решают разные задачи.


В любом случае map — частный случай fold с одной стороны, а с другой — "Visitors &mdash; Equational functions. Frequently foldable"
Так что абсолютно неправым я быть не могу
PoM-PoM 40mm
PoM-PoM 40mm Зачем нужны итераторы?
24.11.2010 10:43
Здравствуйте, Воронков Василий

А что такое порядковый номер? Есть масса контейнеров, где он невозможен или бессмысленен. А так можно писать алгоритм, который слабо зависит от контейнера

, Вы писали:

ВВ>Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В качестве наглядного примера можно взять итераторы в 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#-вые итераторы не дружат совсем.


ВВ>Т.е. итераторы просто сахар? Зачем они нужны?
Воронков Василий
Здравствуйте, PoM-PoM 40mm, Вы писали:

PP4>Здравствуйте, Воронков Василий

PP4>А что такое порядковый номер? Есть масса контейнеров, где он невозможен или бессмысленен. А так можно писать алгоритм, который слабо зависит от контейнера

Например, как? Все способы, предлагаемые линком, чтобы работать с циклическим итератором вида

for (;;) yield 1;


это именно "отщепить" кусочек. Причем кусочек "в цифрах". Будь то First или Take. Вы можете еще раскрутить его через foreach, а в нужный момент дернуть break — но для всего, что можно перебрать через foreach порядковый номер элемента вполне имеет смысл хотя бы рамках этого перебора.

Т.е. линк со своими итераторами и сам никаких особых техник работы с бесконечными последовательностями не предлагает.
VladD2
VladD2
30.11.2010 08:02
Здравствуйте, PoM-PoM 40mm, Вы писали:

PP4>А что такое порядковый номер? Есть масса контейнеров, где он невозможен или бессмысленен. А так можно писать алгоритм, который слабо зависит от контейнера


Это у него такой пунктик. Он весьма странно понимает абстракции. Считает что массив и список это абстракции, а следовательно можно смело пользоваться индексом для выражения порядка элементов списка.
FR
FR Зачем нужны итераторы?
24.11.2010 12:37
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Т.е. итераторы просто сахар? Зачем они нужны?


По сути да сахар, но позволяющий для императивных языков довольно красиво и компактно делать то что в функциональщине
делается через ФВП, например в питоне http://docs.python.org/library/itertools.html практически представляет аналоги
всех базовых ФВП из ФЯ, то же самое и в D http://www.digitalmars.com/d/2.0/phobos/std_algorithm.html и
http://www.digitalmars.com/d/2.0/phobos/std_range.html
Ну и как апофеоз — Rebol в котором итератор базовое основополагающее понятие http://www.rebol.com/docs/core23/rebolcore-6.html

Ну и для строгих функциональных языков родственное итератору понятие — перечисление тоже весьма полезно как средство
обобщенно представлять разные виды последовательностей (конечные и бесконечные, строгие и ленивые) работать с ними
однородно используя тот же набор ФВП, что и для списков и плюс изолировать в них грязь, как пример BatEnum для
OCaml — http://thelema.github.com/batteries-included/hdoc/BatEnum.html
Воронков Василий
Здравствуйте, FR, Вы писали:

FR>Ну и для строгих функциональных языков родственное итератору понятие — перечисление тоже весьма полезно как средство

FR>обобщенно представлять разные виды последовательностей (конечные и бесконечные, строгие и ленивые) работать с ними
FR>однородно используя тот же набор ФВП, что и для списков и плюс изолировать в них грязь, как пример BatEnum для
FR>OCaml — http://thelema.github.com/batteries-included/hdoc/BatEnum.html

Да, вот только "перечислитель" в стиле IEnumerator, подобный которому предлагается и в BatEnum, как я понимаю, это достаточно императивная по своей природе штука. Он предлагает обобщенный способ для работы с коллекциями, но *какой* способ? Скажем, вместо красивого рекурсивного:

let fold f z x::xs = fold f (f z x) xs
       | f z []    = z


Я что буду писать? Это если мне захочется сделать свою функцию, которая что-то делает с энумератором.

Альтернативой тут может быть подход к абстракции для контейнера с другого конца — вот тот же Foldable.
FR
FR
24.11.2010 02:15
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Да, вот только "перечислитель" в стиле IEnumerator, подобный которому предлагается и в BatEnum, как я понимаю, это достаточно императивная по своей природе штука.


Да галимый императив в реализации.
Но интерфейс практически чисто функциональный.

ВВ>Он предлагает обобщенный способ для работы с коллекциями, но *какой* способ?


Очень простой работать через базовые ФВП.

ВВ>Скажем, вместо красивого рекурсивного:


ВВ>
ВВ>let fold f z x::xs = fold f (f z x) xs
ВВ>       | f z []    = z    
ВВ>


ВВ>Я что буду писать? Это если мне захочется сделать свою функцию, которая что-то делает с энумератором.


В случае fold ничего ни будешь, он уже есть, также как и все базовые ФВП.
В этом-то и суть что есть база в виде ФВП и все остальное выражается через нее. Плюс list comprehensions в
батарейках по умолчанию дает Enum.

ВВ>Альтернативой тут может быть подход к абстракции для контейнера с другого конца — вот тот же Foldable.


Ну одно другому не мешает http://thelema.github.com/batteries-included/hdoc/BatEnum.WithMonad.html
Воронков Василий
Здравствуйте, FR, Вы писали:

FR>Да галимый императив в реализации.

FR>Но интерфейс практически чисто функциональный.

Интерфейс — да. Я имею в виду — а как мне писать код, если я решу написать свою функцию, какой-нибудь особенный "fold", работающий с этим самым энумератором. Как я понимаю, через циклы или что-то в этом роде?

FR>В случае fold ничего ни будешь, он уже есть, также как и все базовые ФВП.

FR>В этом-то и суть что есть база в виде ФВП и все остальное выражается через нее.

Получается, что это функциональная обвертка над императивной штукой

FR>Плюс list comprehensions в батарейках по умолчанию дает Enum.


Т.е. list comprehension фактически становится enum comprehension?
FR
FR
24.11.2010 03:03
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Интерфейс — да. Я имею в виду — а как мне писать код, если я решу написать свою функцию, какой-нибудь особенный "fold", работающий с этим самым энумератором. Как я понимаю, через циклы или что-то в этом роде?


Зачем, кошерный способ писать используя базовые ФВП.
Если это не подходит есть низкоуровневые http://thelema.github.com/batteries-included/hdoc/BatEnum.html#6_Usefulfunctions
функции, конечно они по сути императивы, но циклов и изменяемых переменных для работы не требуют.

FR>>В случае fold ничего ни будешь, он уже есть, также как и все базовые ФВП.

FR>>В этом-то и суть что есть база в виде ФВП и все остальное выражается через нее.

ВВ>Получается, что это функциональная обвертка над императивной штукой


Да.

FR>>Плюс list comprehensions в батарейках по умолчанию дает Enum.


ВВ>Т.е. list comprehension фактически становится enum comprehension?


Ну там возможно задавать что получишь на выходе, по умолчанию да enum, но
можно и списки и даже строки и массивы:

[? List: (a, a * a) | a <- 1--10 ?]


[? String: a | a <-   'a'  -- 'z'  ?]
D. Mon
D. Mon Зачем нужны итераторы?
24.11.2010 03:33
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.


В энергичных языках есть важная разница: кто кого вызывает и контролирует — основной код просит у итератора значение или итератор (генератор) пихает очередное значение основному коду. Простой пример: у нас есть 100 итераторов, представляющих строки в разных файлах (неизвестной длины), и мы хотим вывести все первые строки, потом все вторые, все третьи и т.д. С итераторами а-ля C# или ленивыми списками это делается очень просто. А как это будет выглядеть с генераторами/ФВП?
MasterZiv
MasterZiv Зачем нужны итераторы?
25.11.2010 08:32
On 24.11.2010 12:47, Воронков Василий wrote:

> Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В

> качестве наглядного примера можно взять итераторы в C# или даже более
> продвинутую реализацию. Неважно.

Вообще итераторы не нужны. Но придуманы они были для того, чтобы отделить
алгоритмы обработки коллекций от способов доступа к этим коллекциям,
чтобы алгоритмы были бы универсальными.
Posted via RSDN NNTP Server 2.1 beta
Sinclair
Sinclair Зачем нужны итераторы?
27.11.2010 12:42
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик.

Вот это как-то странно.

ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.

Ну, во-первых, можно даже сделать более сильное утверждение: итераторы изоморфны событиям.
В твоём примере IEnumerable<int> лёгким движением руки превращается в Action<int>. Это — частный случай того, о чём много и убедительно пишет Барт.

Понятно, что сразу получаем обратный вопрос: а зачем, собственно, нужны события и возможность передавать свою функцию куда-то туда, откуда можно брать и вычитывать значения?

Ответ, конечно же, очевиден: удобным бывает разное представление одного и того же алгоритма. Кроме этого, надо понимать, что дотнет принципиально многоязыковая среда. Потреблять итератор можно и из языка, в котором нет замыканий; потреблять твой вариант — крайне тяжело.
Так что, таки да: "с точки зрения юзабилити это, конечно, менее удобно, но вот, собственно, и все."

Ну и, конечно же, как всегда, рекомендую почитать Липперта на тему применибельности CPS в C#.
Воронков Василий
Здравствуйте, Sinclair, Вы писали:

S>Ответ, конечно же, очевиден: удобным бывает разное представление одного и того же алгоритма.


Зачем?

S>Кроме этого, надо понимать, что дотнет принципиально многоязыковая среда. Потреблять итератор можно и из языка, в котором нет замыканий; потреблять твой вариант — крайне тяжело.


Язык, в котором нет функций-генераторов, сможет только вызвать такую функцию, и все. Причем для удобной работы с этими функциями все равно должна быть некоторая поддержка со стороны языка, иначе "крайне тяжело" будет работать и с обычными итераторами. Яркий пример — МС++, где даже for each для IEnumerable не было.
Первоклассные же функции имитируются в C# через делегаты, ибо только делегат можно передать в другую функцию. Делегат же — это уже не фича C#. Таким образом, ФВП, написанные на C#, вполне юзабельны и из других .NET-языков — насколько "тяжело" будет с ними работать зависит опять же от того, поддерживает ли язык специальный сахар для делегатов, умеет ли его компилятор создавать замыкания и пр.

Я не вижу здесь какой-то кардинальной разницы.

S>Так что, таки да: "с точки зрения юзабилити это, конечно, менее удобно, но вот, собственно, и все."

S>Ну и, конечно же, как всегда, рекомендую почитать Липперта на тему применибельности CPS в C#.

Меня C# вообще мало интересует. Применимо там CPS или неприменимо, насколько оно совместимо с C# программистами и проч. Мне как раз интересно, были ли другие причины, кроме "в .NET есть и другие языки" или "CPS непонятен людям" сделать все так, а не иначе. Липперт как раз на этот вопрос не отвечает.
Sinix
Sinix
27.11.2010 06:41
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Меня C# вообще мало интересует. Применимо там CPS или неприменимо, насколько оно совместимо с C# программистами и проч.

Странный вы человек, сначала спрашиваете "нафига итераторы?", а на "в шарпе ими удобно пользоваться" отвечаете, что шарп вас не интересует

ВВ>Мне как раз интересно, были ли другие причины, кроме "в .NET есть и другие языки" или "CPS непонятен людям" сделать все так, а не иначе. Липперт как раз на этот вопрос не отвечает.


Отвечает, и неоднократно. Шарп — мейнстрим; соображения практического удобства ценятся на голову выше, чем абстрактная чистота. Для повышения ЧСВ есть куча других языков и платформ, к чему эти регулярные крестовые походы на <подставить фичу>?
Воронков Василий
Здравствуйте, Sinix, Вы писали:

ВВ>>Мне как раз интересно, были ли другие причины, кроме "в .NET есть и другие языки" или "CPS непонятен людям" сделать все так, а не иначе. Липперт как раз на этот вопрос не отвечает.

S>Отвечает, и неоднократно.

Что отвечает? "Шарп — мейнстрим" это не ответ. Не тот ответ, который мне интересен.

S>Шарп — мейнстрим; соображения практического удобства ценятся на голову выше, чем абстрактная чистота. Для повышения ЧСВ есть куча других языков и платформ, к чему эти регулярные крестовые походы на <подставить фичу>?


Не занимаюсь я никакими крестовыми походами на <подставить фичу>. Говоря, что шарп меня интересует, я имею в виду, что не хочу склонять обсуждение темы к тому, насколько оправдано существование генераторов наряду с замыканиями конкретно в C#. Хотя бы потому что ответы будут "в стиле Липперта". Это не значит, что ответы плохие. Меня просто интересует другое.

Будет проще, если я спрошу, оправдано ли в неком абстрактном языке существование наряду с замыканиями функций-генераторов, какие задачи они могут решать и почему эти задачи не могут быть решены через ФВП с таким же удобством. Неужели обсуждать сферических коней в вакууме проще?
Sinix
Sinix
28.11.2010 04:58
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Что отвечает? "Шарп — мейнстрим" это не ответ. Не тот ответ, который мне интересен.


Так вам уже по-всякому ответили. Делегаты рулят для простого расширения поведения. Итераторы — для работы с последовательностями. То, что одно можно выразить через другое, не значит что остаться должен кто-то один.

Если убирать итераторы — что вы будете делать с foreach? Как вы реализуете linq? Представляете примерный объём работы по переделке рантайма/BCL/документации/инструментария? Нафига?

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


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

Поймите, никого не колышет язык сам по себе, побеждает связка "язык+библиотеки+инструментарий+документация+community". Вы можете сделать сколько угодно сверхфичастых сфероязыков, но пока на них не будет удобно как клепать формочку для студенческой лабораторной, так и писать софт уровня студии — ваш гипотетический язык так и останется игрушкой для гиков.
Воронков Василий
Здравствуйте, Sinix, Вы писали:

ВВ>>Что отвечает? "Шарп — мейнстрим" это не ответ. Не тот ответ, который мне интересен.

S>Так вам уже по-всякому ответили. Делегаты рулят для простого расширения поведения. Итераторы — для работы с последовательностями. То, что одно можно выразить через другое, не значит что остаться должен кто-то один.
S>Если убирать итераторы — что вы будете делать с foreach? Как вы реализуете linq? Представляете примерный объём работы по переделке рантайма/BCL/документации/инструментария? Нафига?

Причем тут переделка BCL?

S>Поймите, никого не колышет язык сам по себе, побеждает связка "язык+библиотеки+инструментарий+документация+community". Вы можете сделать сколько угодно сверхфичастых сфероязыков, но пока на них не будет удобно как клепать формочку для студенческой лабораторной, так и писать софт уровня студии — ваш гипотетический язык так и останется игрушкой для гиков.


Форум под названием "Философия программирования" должен быть посвящен по-вашему обсуждению клепания формочек?
Мне одно непонятно — если единственное, что вы можете ответить, это апеллировать к "практичности" и мейнстриму, зачем вообще что-то отвечать? Мне обязательно каждый раз оправдываться за то, что я наступил кому-то на любимую фичу?
Sinix
Sinix
28.11.2010 08:39
Здравствуйте, Воронков Василий, Вы писали:

S>>Если убирать итераторы — что вы будете делать с foreach? Как вы реализуете linq? Представляете примерный объём работы по переделке рантайма/BCL/документации/инструментария? Нафига?


ВВ>Причем тут переделка BCL?

[К.О]
1. Множество методов из BCL принимают/возвращают IEnumerable
2. Придётся протаскивать везде ФВП, да ещё так, чтобы они по производительности не отстали от state-машниы, в которую разворачивается итератор.
[/К.О]

S>>Поймите, никого не колышет язык сам по себе, побеждает связка "язык+библиотеки+инструментарий+документация+community". Вы можете сделать сколько угодно сверхфичастых сфероязыков, но пока на них не будет удобно как клепать формочку для студенческой лабораторной, так и писать софт уровня студии — ваш гипотетический язык так и останется игрушкой для гиков.


ВВ>Форум под названием "Философия программирования" должен быть посвящен по-вашему обсуждению клепания формочек?

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

ВВ>Мне одно непонятно — если единственное, что вы можете ответить, это апеллировать к "практичности" и мейнстриму, зачем вообще что-то отвечать? Мне обязательно каждый раз оправдываться за то, что я наступил кому-то на любимую фичу?


Да можно сделать всё что угодно; в куче языков итераторов в помине нет, и они от этого не сильно страдают. Я всего лишь отметил, что в мейнстрим-языках высокий штиль если и проглядывает, то в первую очередь в упрощённо-приземлённом виде. Потому что людям в первую очередь нужен инструмент для решения насущных проблем; способов изнасиловать мозг и так по горло и выше.

Забавная параллель:

Ты так и не определил, что же такое правильный инструмент. ДАвай я это сделаю за тебя. Из твоего ответа видно, что ты считаешь правильным инструментом тот, который напрямую предназначен для решения этой задачи.
Так ты считаешь?

Так вот реальный мир против. Правильный инструмент это тот который решает поставленную задачу с наименьшими затратами (и сил и времени и всего остального).

Sinclair
Sinclair
29.11.2010 05:22
Здравствуйте, Воронков Василий, Вы писали:


ВВ>Зачем?

Очевидно, для компактности и воспринимаемости записанного кода.

S>>Кроме этого, надо понимать, что дотнет принципиально многоязыковая среда. Потреблять итератор можно и из языка, в котором нет замыканий; потреблять твой вариант — крайне тяжело.


ВВ>Язык, в котором нет функций-генераторов, сможет только вызвать такую функцию, и все. Причем для удобной работы с этими функциями все равно должна быть некоторая поддержка со стороны языка, иначе "крайне тяжело" будет работать и с обычными итераторами. Яркий пример — МС++, где даже for each для IEnumerable не было.

ВВ>Первоклассные же функции имитируются в C# через делегаты, ибо только делегат можно передать в другую функцию. Делегат же — это уже не фича C#. Таким образом, ФВП, написанные на C#, вполне юзабельны и из других .NET-языков — насколько "тяжело" будет с ними работать зависит опять же от того, поддерживает ли язык специальный сахар для делегатов, умеет ли его компилятор создавать замыкания и пр.

ВВ>Я не вижу здесь какой-то кардинальной разницы.

Сравним два фрагмента кода на воображаемом языке:

1.
bool hadString = false;
using(StreamWriter sw = OpenWriteStream())
foreach(var s in source.ReadStrings())
{
  if (hadString)
    sw.Write(", ");
  else 
    hadString = true;
  sw.Write(s);
}

2.
using(StreamWriter sw = OpenWriteStream())
{
  var sProcessor = new sProcessor(sw);
  source.ReadStrings(sProcessor.Process)
}
...

private class sProcessor
{
  private bool _hadString = false;
  private StreamWriter _sw;
  public sProcessor(StreamWriter sw)
  {
    _sw = sw;
  }
  public void Process(string s)
  {
    if (_hadString)
      _sw.Write(", ");
    else 
      _hadString = true;
    _sw.Write(s);
  }
}

В данном случае, разница мне кажется очевидной.

ВВ>Меня C# вообще мало интересует. Применимо там CPS или неприменимо, насколько оно совместимо с C# программистами и проч. Мне как раз интересно, были ли другие причины, кроме "в .NET есть и другие языки" или "CPS непонятен людям" сделать все так, а не иначе. Липперт как раз на этот вопрос не отвечает.

По-моему, вы капризничаете. Фичи не существуют "в воздухе", сами по себе. Они существуют в контексте некоторого фреймворка. В данном конкретном фреймворке есть данности
— существующий интерфейс IEnumerable
— паттерны по его поддержке
— ограничения на то, что можно/нельзя использовать в замыкании

С учётом этих данностей и было принято решение. Как я уже сказал, глобально — никаких причин не делать поддержку "обратных вызовов" нету.
Воронков Василий
Здравствуйте, Sinclair, Вы писали:

ВВ>>Зачем?

S>Очевидно, для компактности и воспринимаемости записанного кода.

Это причина, по которой в язык добавляют сахар. Но это не причина для добавления новых фич.

Зайдем с другой стороны. Предположим, что у нас есть воображаемый язык Blab. В языке Blab есть первоклассные функции и замыкания. Язык Blab существует сам по себе, без всяких дотнетов — более того, у него вообще свой собственный бэкенд, спроектированный специально для Blab-а.

Также предположим, что Blab — это функциональный язык.

Blab-еры как-то прознали о том, что существует такая хитрая фича — функции, которые могут возвращать значение несколько раз и к тому же умеют сохранять свое состояние между вызовами. На первый взгляд Блаберам кажется, что фича эта странная и стремная: какие-то побочные эффекты непонятные, вызов и использование таких функций будет сильно отличаться от обычных, с рекурсией внутри них тоже косяки, потом опять же на первый взгляд им кажется, что ничего эта фича в сущности не дает-то. Query Comprehension? Да это же простые комбинаторы. Отложенные вычисления? Так у Блабером для этого санки есть. А на санках и ленивые списки строятся. Через такие функции можно получить универсальный интерфейс для работы с контейнерами? Ну во-первых есть Foldable, а во-вторых — получить-то можно, а как работать с этими контейнерами придется, в императивном стиле? А в Блабе может и вообще циклов не быть.

Однако вот друзья-товарищи из Питона и Руби говорят, что фича мировая. Какие причины эти самые товарищи могут привести, чтобы убедить блаберов, что генераторы их языку, несмотря на вышесказанное, все же не помешают?
Sinix
Sinix
29.11.2010 09:40
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Однако вот друзья-товарищи из Питона и Руби говорят, что фича мировая. Какие причины эти самые товарищи могут привести, чтобы убедить блаберов, что генераторы их языку, несмотря на вышесказанное, все же не помешают?


Никакие. Потому что фичи добавляются не сами по себе, а в комплексе, чтобы сделать язык удобным для решения определённого класса задач. Выбор набора фич определяется исключительно религиозными предпочтениями + (если повезёт) целевой аудиторией языка.

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

Аналогичный подход применяется (в идеале) и при дальнейшем развитии языка, иначе он моментально превратится в помойку. И, увы, добавить что-то новое, чтобы оно не выглядело чужеродным обвесом, но и не ломало совместимость с каждым релизом всё сложнее и сложнее.

Так что пока блаберам не потребуется добавить ряд фич, которые удобно решаются на итераторах (и оочень неудобно решать ч/з ФВП), итераторов в блабе не будет. По той же причине в шарпе нет ФВП в чистом виде.
Sinclair
Sinclair
29.11.2010 11:45
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Blab-еры как-то прознали о том, что существует такая хитрая фича — функции, которые могут возвращать значение несколько раз и к тому же умеют сохранять свое состояние между вызовами.

А можно мне показать описание вот этой хитрой фичи? Потому что я знаком только с дотнетной реализацией итераторов, так вот она ведёт себя вовсе не так, как тут описано. Питоновая реализация, судя по описанию, тоже совсем не то.

ВВ> На первый взгляд Блаберам кажется, что фича эта странная и стремная: какие-то побочные эффекты непонятные, вызов и использование таких функций будет сильно отличаться от обычных, с рекурсией внутри них тоже косяки, потом опять же на первый взгляд им кажется, что ничего эта фича в сущности не дает-то.

Ну, если делать такую странную и стрёмную фичу, то конечно же непонятно, в чём её смысл.
Скорее всего, в Блабе эту фичу сделать и вовсе не получится. Он же функциональный, поэтому в нём не будет изменяемого итератора. Зато концепция ленивого списка может быть встроена в него с самого начала.
Воронков Василий
Здравствуйте, Sinclair, Вы писали:

ВВ>>Blab-еры как-то прознали о том, что существует такая хитрая фича — функции, которые могут возвращать значение несколько раз и к тому же умеют сохранять свое состояние между вызовами.

S>А можно мне показать описание вот этой хитрой фичи? Потому что я знаком только с дотнетной реализацией итераторов, так вот она ведёт себя вовсе не так, как тут описано. Питоновая реализация, судя по описанию, тоже совсем не то.

Со стороны пользователя практически так она себя и ведет. Только интерфейс вызова выглядит как (Current/MoveNext) и представлен в виде объекта, который, собственно, и инкапсулирует в себе состояние функции, ее как бы стек. В Питоне по-моему несколько иначе. И завершение исполнения там сигнализируется выбросом исключения.

S>Ну, если делать такую странную и стрёмную фичу, то конечно же непонятно, в чём её смысл.

S>Скорее всего, в Блабе эту фичу сделать и вовсе не получится. Он же функциональный, поэтому в нём не будет изменяемого итератора.

Зависит. В Ocaml-е же есть BatEnum. Т.е. что-то же заставило OCaml-овцев, в отличие от Блаберов, таки прийти к выводу, что итераторы это есть хорошо, несмотря на то, что в языке вроде как нет и недостатка в фичах из Блаба.

S>Зато концепция ленивого списка может быть встроена в него с самого начала.


Ленивый список — это просто x :: thunk, т.е. это и концепцией-то назвать сложно, можно сказать, особенность самой структуры данных.
Sinclair
Sinclair
29.11.2010 03:34
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Со стороны пользователя практически так она себя и ведет. Только интерфейс вызова выглядит как (Current/MoveNext) и представлен в виде объекта, который, собственно, и инкапсулирует в себе состояние функции, ее как бы стек. В Питоне по-моему несколько иначе. И завершение исполнения там сигнализируется выбросом исключения.

Нет. Со стороны пользователя эта фича ведёт себя совершенно иначе. Есть foreach, и он магическим образом пользуется Current/MoveNext/Dispose. Пользователю всё равно, что там унутре за неонка. К примеру, C++ итераторы устроены точно так же, несмотря на отстутствие замыканий и чего бы то ни было другого.
Резюме: функция со стороны пользователя никоим образом не умеет "возвращать значение несколько раз" или "сохранять своё состояние". Функция возвращает значение ровно однажды, это значение типа IEnumerator<T>. Насколько я успел понять диагональным методом, в питоне всё строго так же.

Реализация внутренностей c yield return строго ортогональна потреблению с foreach.

ВВ>Зависит. В Ocaml-е же есть BatEnum. Т.е. что-то же заставило OCaml-овцев, в отличие от Блаберов, таки прийти к выводу, что итераторы это есть хорошо, несмотря на то, что в языке вроде как нет и недостатка в фичах из Блаба.

Ну, тогда, наверное, надо спросить окамловцев. Я с BatEnum совершенно незнаком — почему и прошу показать мне описание. Задавать вопрос про BatEnum в терминах шарпа я считаю несколько экстравагантным.

S>>Зато концепция ленивого списка может быть встроена в него с самого начала.


ВВ>Ленивый список — это просто x :: thunk, т.е. это и концепцией-то назвать сложно, можно сказать, особенность самой структуры данных.

Ну так концепция ленивого списка == IEnumerable. Если она уже есть, то ещё один IEnumerable не нужен.
minorlogic
minorlogic Зачем нужны итераторы?
28.11.2010 02:20
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.


Гм... точно ? т.е. быструю сортировку успешно выполнить через замыкания ?
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Воронков Василий
Здравствуйте, minorlogic, Вы писали:

ВВ>>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.

M>Гм... точно ? т.е. быструю сортировку успешно выполнить через замыкания ?

А что мешает-то? И причем тут вообще итераторы?
minorlogic
minorlogic
29.11.2010 09:22
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Здравствуйте, minorlogic, Вы писали:


M>>Гм... точно ? т.е. быструю сортировку успешно выполнить через замыкания ?


ВВ>А что мешает-то? И причем тут вообще итераторы?


Возможно я заблуждаюсь , но не видел красивой и эфективной сортировки без итераторов. Дайте линк.
Воронков Василий
Здравствуйте, minorlogic, Вы писали:

ВВ>>А что мешает-то? И причем тут вообще итераторы?

M>Возможно я заблуждаюсь , но не видел красивой и эфективной сортировки без итераторов. Дайте линк.

Вы мне сначала покажите то, что называете сортировкой на итераторах.
minorlogic
minorlogic
29.11.2010 11:06
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Здравствуйте, minorlogic, Вы писали:


ВВ>>>А что мешает-то? И причем тут вообще итераторы?

M>>Возможно я заблуждаюсь , но не видел красивой и эфективной сортировки без итераторов. Дайте линк.

ВВ>Вы мне сначала покажите то, что называете сортировкой на итераторах.


http://en.wikipedia.org/wiki/Quicksort
Воронков Василий
Здравствуйте, minorlogic, Вы писали:

ВВ>>Вы мне сначала покажите то, что называете сортировкой на итераторах.

M>http://en.wikipedia.org/wiki/Quicksort

http://en.wikipedia.org/wiki/Recursion
D. Mon
D. Mon
29.11.2010 09:46
Здравствуйте, Воронков Василий, Вы писали:

M>>Гм... точно ? т.е. быструю сортировку успешно выполнить через замыкания ?


ВВ>А что мешает-то? И причем тут вообще итераторы?


Многие помнят, как просто выражается квиксорт ("ненастоящий") на Хаскеле со списками: фильтруем, вызываем рекурсивно, соединяем.
А как это будет выглядеть с ФВП (без итераторов и ленивых списков) Когда есть функция, куда можно передать свою, и она получит все значения последовательности.

И еще попрошу все же показать, как будет выглядеть мой пример с последовательным выводом строк из сотни файлов.
Воронков Василий
Здравствуйте, D. Mon, Вы писали:

DM>Многие помнят, как просто выражается квиксорт ("ненастоящий") на Хаскеле со списками: фильтруем, вызываем рекурсивно, соединяем.

DM>А как это будет выглядеть с ФВП (без итераторов и ленивых списков) Когда есть функция, куда можно передать свою, и она получит все значения последовательности.

Во-первых, откуда вы взяли ограничение "без ленивых списков"? Они-то как под нож попали? А на ленивых списках тот же Линк имитируется один в один.
Во-вторых, сортировка в Линке построена в сущности на континуэшине. Там компаратор работает примерно так:

comp f next x y | r == 0    = next x y
                | otherwise = r
                where r = f x y


И сдается мне, что итераторы тут играют сильно побочную роль.

DM>И еще попрошу все же показать, как будет выглядеть мой пример с последовательным выводом строк из сотни файлов.


Было бы неплохо, если вы бы свой пример тоже показали. Я не очень понимаю, что имеется в виду под "последовательным выводом строк из сотни файлов".
D. Mon
D. Mon
29.11.2010 11:21
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Здравствуйте, D. Mon, Вы писали:


DM>>Многие помнят, как просто выражается квиксорт ("ненастоящий") на Хаскеле со списками: фильтруем, вызываем рекурсивно, соединяем.

DM>>А как это будет выглядеть с ФВП (без итераторов и ленивых списков) Когда есть функция, куда можно передать свою, и она получит все значения последовательности.

ВВ>Во-первых, откуда вы взяли ограничение "без ленивых списков"? Они-то как под нож попали? А на ленивых списках тот же Линк имитируется один в один.


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

ВВ>Во-вторых, сортировка в Линке построена в сущности на континуэшине. Там компаратор работает примерно так:


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

ВВ>И сдается мне, что итераторы тут играют сильно побочную роль.


Речь о том, что как в хаскеле мы можем просто фильтровать и соединять списки, получая квиксорт в три строчки, так и с итераторами — мы можем их фильтровать и соединять, как списки.

DM>>И еще попрошу все же показать, как будет выглядеть мой пример с последовательным выводом строк из сотни файлов.


ВВ>Было бы неплохо, если вы бы свой пример тоже показали. Я не очень понимаю, что имеется в виду под "последовательным выводом строк из сотни файлов".


Абстрагируясь от работы с файлами, суть такова. Пусть у нас есть несколько итераторов, выдающих последовательности строк "fileN lineM". Нужно вывести сначала все первые строчки, затем вторые и т.д.

Простите за мой французский, но я напишу на Окамле
(* функция, создающая один итератор *)
let file_iter n = Enum.init 10 (fun i -> Printf.sprintf "file%d line%d" n i)

(* список таких итераторов *)
let files = List.init 10 (fun n -> file_iter n)

(* вывод строк по одной *)
let rec show_lines lst =
  let continue = List.fold_left (fun not_end it -> (* проходимся по списку итераторов *)
    match Enum.get it with (* берем из каждого по одной строке *)
    | None -> not_end 
    | Some line -> print_endline line; true) false lst  in (* выводим ее, и определяем, нужно ли продолжать *)
  if continue then show_lines lst else ();; (* пока все не кончились, повторяем *)

show_lines files (* запускаем *)


Теперь прошу аналог с ФВП и, если угодно, CPS.
Воронков Василий
Здравствуйте, D. Mon, Вы писали:

DM>С ленивыми списками разница между итераторами и генераторами размывается. Я специально выше говорил про энергичные языки.


Стоп. Генератор — это вполне конкретная фишка. Это функция, которая может возвращать свое значение несколько раз и может сохранять свое состояние между вызовами. Итератор — ну это просто паттерн.

ВВ>>Во-вторых, сортировка в Линке построена в сущности на континуэшине. Там компаратор работает примерно так:


DM>Кажется, это сравнение двух последовательностей. К сортировке оно не относится.


Это не сравнение двух последовательностей, это генерация ключей, по которым производится сортировка. Именно так и работает "цепочечная" сортировка в Linq вида OrderBy(...).ThenBy(...).ThenBy(...). Т.е. построена она на CPS по сути.

ВВ>>Было бы неплохо, если вы бы свой пример тоже показали. Я не очень понимаю, что имеется в виду под "последовательным выводом строк из сотни файлов".

DM>Абстрагируясь от работы с файлами, суть такова. Пусть у нас есть несколько итераторов, выдающих последовательности строк "fileN lineM". Нужно вывести сначала все первые строчки, затем вторые и т.д.

DM>Простите за мой французский, но я напишу на Окамле

DM>Теперь прошу аналог с ФВП и, если угодно, CPS.

Ну например, мне тут по соседству приводили пример реализации паттерна Iteratee на Хаскеле.
http://okmij.org/ftp/Haskell/Iteratee/DEFUN08-talk-notes.pdf
Вкратце:

fold :: (a -> b -> b) -> b -> IntMap a -> b
fold f z coll  (f an ...(f a2 (f a1 z)))
prod = fold (*) 1 coll
       (an * ...(a2 * (a1 * 1)))
prodbut n = snd (fold iteratee (n,1) coll)
  where iteratee a (n,s) =
      if n <= 0 then (n,a*s) else (n-1,s)


Вообще при работе с файлами у нас и так есть некий итератор, поток, stream. Я могу написать вариант через CPS, но все равно я для начала должен исходить из того, откуда именно происходит чтение.
Пока получается, что ваша задача звучит так: у вас есть итератор, попробуйте с ним работать без итератора.
D. Mon
D. Mon
29.11.2010 03:22
Здравствуйте, Воронков Василий, Вы писали:

DM>>Абстрагируясь от работы с файлами, суть такова. Пусть у нас есть несколько итераторов, выдающих последовательности строк "fileN lineM". Нужно вывести сначала все первые строчки, затем вторые и т.д.


ВВ>Ну например, мне тут по соседству приводили пример реализации паттерна Iteratee на Хаскеле.

ВВ>http://okmij.org/ftp/Haskell/Iteratee/DEFUN08-talk-notes.pdf
ВВ>Вкратце:

Ну вот и напишите, как пройтись параллельно по нескольким итераторам. (не в смысле многоядерности, а в смысле порядка выводимых элементов)

ВВ>Вообще при работе с файлами у нас и так есть некий итератор, поток, stream.


Да, есть, в окамле тоже. Это несущественно. Покажите, как с CPS по нескольким файлам пройтись.
D. Mon
D. Mon
29.11.2010 03:52
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Стоп. Генератор — это вполне конкретная фишка. Это функция, которая может возвращать свое значение несколько раз и может сохранять свое состояние между вызовами. Итератор — ну это просто паттерн.


Прошу прощения, я могу быть неточным в терминах. Под итератором я имел в виду IEnumerable из C# и Enum из Окамла — пассивный объект, выдающий элементы по запросу. Под генератором я имел в виду описанный в первом посте Func<T,Int32,Boolean> или его аналог в Руби (там, где for по последовательности превращается в вызов метода с передачей тела цикла в виде ФВП). Именно между ними я вижу принципиальную разницу, и мне интересно, как первое выразить через второе.
Воронков Василий
Здравствуйте, D. Mon, Вы писали:

DM>Здравствуйте, Воронков Василий, Вы писали:


ВВ>>Здравствуйте, D. Mon, Вы писали:


DM>>>Многие помнят, как просто выражается квиксорт ("ненастоящий") на Хаскеле со списками: фильтруем, вызываем рекурсивно, соединяем.

DM>>>А как это будет выглядеть с ФВП (без итераторов и ленивых списков) Когда есть функция, куда можно передать свою, и она получит все значения последовательности.

ВВ>>Во-первых, откуда вы взяли ограничение "без ленивых списков"? Они-то как под нож попали? А на ленивых списках тот же Линк имитируется один в один.


DM>С ленивыми списками разница между итераторами и генераторами размывается. Я специально выше говорил про энергичные языки.


ВВ>>Во-вторых, сортировка в Линке построена в сущности на континуэшине. Там компаратор работает примерно так:


DM>Кажется, это сравнение двух последовательностей. К сортировке оно не относится.


ВВ>>И сдается мне, что итераторы тут играют сильно побочную роль.


DM>Речь о том, что как в хаскеле мы можем просто фильтровать и соединять списки, получая квиксорт в три строчки, так и с итераторами — мы можем их фильтровать и соединять, как списки.


DM>>>И еще попрошу все же показать, как будет выглядеть мой пример с последовательным выводом строк из сотни файлов.


ВВ>>Было бы неплохо, если вы бы свой пример тоже показали. Я не очень понимаю, что имеется в виду под "последовательным выводом строк из сотни файлов".


DM>Абстрагируясь от работы с файлами, суть такова. Пусть у нас есть несколько итераторов, выдающих последовательности строк "fileN lineM". Нужно вывести сначала все первые строчки, затем вторые и т.д.


DM>Простите за мой французский, но я напишу на Окамле

DM>
DM>(* функция, создающая один итератор *)
DM>let file_iter n = Enum.init 10 (fun i -> Printf.sprintf "file%d line%d" n i)

DM>(* список таких итераторов *)
DM>let files = List.init 10 (fun n -> file_iter n)

DM>(* вывод строк по одной *)
DM>let rec show_lines lst =
DM>  let continue = List.fold_left (fun not_end it -> (* проходимся по списку итераторов *)
DM>    match Enum.get it with (* берем из каждого по одной строке *)
DM>    | None -> not_end 
DM>    | Some line -> print_endline line; true) false lst  in (* выводим ее, и определяем, нужно ли продолжать *)
DM>  if continue then show_lines lst else ();; (* пока все не кончились, повторяем *)

DM>show_lines files (* запускаем *)
DM>


DM>Теперь прошу аналог с ФВП и, если угодно, CPS.


Кстати, если считать, что на входе у нас ['a]], т.е. структура данных, про которую мы знаем, как ее можно перебирать, то реализацию можно сделать практически такую же как у тебя:

let lst = [ [ 1;2;3 ]; [ 4;5;6 ]; [ 7;8;9 ]; ];;                  

let rec iter = function
    | x::xs -> ( Printf.printf "%d\n" x; `Some (xs) )
    | []    -> `None;;
            
let rec filter f lst = 
    let rec filter' = function
        | x::xs -> (match f x with
                   | `Some(v) -> v :: filter' xs
                   | `None    -> filter' xs)
        | []    -> []
    in
    match filter' lst with
    | []  -> ()
    | nl  -> filter f nl;;
        
filter iter lst;;


А для того, чтобы эта шутка работало не только для частного случая список, но и для общего случая — достаточно абстракции Foldable.
D. Mon
D. Mon
29.11.2010 03:47
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Кстати, если считать, что на входе у нас ['a]], т.е. структура данных, про которую мы знаем, как ее можно перебирать, то реализацию можно сделать практически такую же как у тебя:


Спасибо! С ExtLib и чуть обобщив это выглядит так:
open ExtLib;;

let lst = [ [ 1;2;3 ]; [ 4;5;6 ]; [ 7;8;9 ]; ];;                  

let rec iter action = function
  | x::xs -> action x; Some xs 
  | []    -> None;;
            
let rec filter f lst = 
  match List.filter_map f lst with
  | []  -> ()
  | nl  -> filter f nl;;
        
filter (iter (Printf.printf "%d ")) lst;;


ВВ>А для того, чтобы эта шутка работало не только для частного случая список, но и для общего случая — достаточно абстракции Foldable.


Здесь нам нужно, чтобы для нашей последовательности типа 'elt seq (последовательность элементов типа 'elt) была доступна функция
iter : ('elt -> 'elt seq option) -> 'elt seq -> 'elt seq option

Осталось понять, как ее реализовать для той штуки, что в описана первом посте вторым вариантом — где 'elt seq это 'elt -> int -> bool
(Func<T,Int32,Boolean>).
minorlogic
minorlogic
29.11.2010 11:23
Короткие примеры на функциональных языках , мягко говоря неэфективны, зачастую требуют лишних копирований, лишней памяти и т.п.

Я имел в виду именно эфективную реализацию.
velkin
velkin Зачем нужны итераторы?
05.02.2012 11:53
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Т.е. итераторы просто сахар? Зачем они нужны?


Итератор является шаблоном проектирования. Как идея он не зависит от конкретной реализации в каком-либо языке, или в библиотеке алгоритмов. Нужен он:

1. для последовательного прохождения по любой структуре данных (линейный список, граф и так далее)
2. подмены передачи последовательности прохождения по структуре в алгоритме (чтобы не переписывать сам алгоритм множество раз (китайский код, копипаста))

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

типичный итератор:
a. установка на начало последовательности (возвращает итератор)
b. проверка на конец последовательности (возвращает конец или нет)
c. переход к следующему элементу последовательности (ничего не возвращает)
d. получить текущий элемент структуры (возвращает текущий элемент структуры на котором остановилась последовательность)

Даже если изменить строение итератора с классического на иной, всё равно исходная идея отделения логики навигации по структуре данных останется. Можно слить получение текущего элемента с переходом на следующий элемент.

(d)получение текущего элемента + (c)переход на следующий элемент = получить следующий элемент

Получится тоже вполне себе итератор, только при использовании элемента более одного раза придётся куда-то записывать способ указания на сам элемент. В трансформации итератора можно пойти и дальше, получив то, что здесь было названо функцией замыкания. Его логика возможно потребует более сложного решения, чем take + 1, где take типа int. Иными словами как бы не меняли итератор, в итоге скорее всего получим менее удобный и более корявый итератор, чем тот, который уже стал классикой, но это будет тоже итератор. А иначе попросту лишимся возможностей пунктов 1 и 2.

P.S. Ещё существует потоковый итератор, обычно ему приписывают I/O, то есть ввод/вывод. Хотя идея схожая, с разницей, что данные поступают не элементами, а кусочками. Тоже своего рода понятие. Мышления на более высоких уровнях абстракции, цель которых не усложнить, а упростить жизнь разработчикам.