Интересные обсуждения
темы заинтересовавшие velkin
Необходимость итераторов в программировании
24.11.2010
|
Воронков Василий
|
Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В качестве наглядного примера можно взять итераторы в C# или даже более продвинутую реализацию. Неважно.
Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
Попробую объяснить, что я имею в виду. Положим, некий код вида:
Можно переписать вот так:
Можно представить так и работу с бесконечными последовательностями, если действие будет описывать как Func<T,Int32,Boolean>, где второй параметр — порядковый номер элемента, а первый сам элемент. Возвращается же флажок, по которому определяются следует ли нам продолжать. Создавать такую функцию можно через простейший комбинатор вида:
Т.е. с точки зрения юзабилити это, конечно, менее удобно, но вот, собственно, и все.
В принципе у итераторов как частного случая корутин должны быть более широкие применения вроде как, но все, о чем я могу подумать, прекрасно выражается через ФВП. Причем вариант с ФВП отлично дружит с continuation-ами, с которыми те же 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#-вые итераторы не дружат совсем.
Т.е. итераторы просто сахар? Зачем они нужны?
24.11.2010 98 комментариев |
ВВ>Т.е. итераторы просто сахар? Зачем они нужны?
Мне кажется, ты перевёл всё в CPS. Представь как будет выглядеть цепочка функций без итераторов.
ВВ>>Т.е. итераторы просто сахар? Зачем они нужны?
L>Мне кажется, ты перевёл всё в CPS. Представь как будет выглядеть цепочка функций без итераторов.
На C# — не очень. На каком-нибудь более другом языке — вполне нормально.
У меня возникает вообще впечатление, что итераторы сами по себе мешают писать функциональный код. С рекурсией у них как-то тухло. Сами по себе они выступают в виде этакой альтернативы более привычным вещам, вроде созданию ФВП, композиции селекторов. Или может, есть какие-то более правильные итераторы, о которых я не знаю?
Вообще вопрос можно задать так — почему итераторы не нужны в Хаскеле?
ВВ>Вообще вопрос можно задать так — почему итераторы не нужны в Хаскеле?
Потому что он весь из себя ленивый — ты свободно можешь создать бесконечный список и вернуть его из фнкции.
ВВ>>Вообще вопрос можно задать так — почему итераторы не нужны в Хаскеле?
L>Потому что он весь из себя ленивый — ты свободно можешь создать бесконечный список и вернуть его из фнкции.
То, что я могу сделать *на самом деле* — это через итератор выразить некую non-strict последовательность *вычислений*. Это же я могу сделать и без итераторов через CPS. А для того, чтобы создать а) *список* и б) ленивый список с мемоизацией — мне и с итераторами придется повозиться. Т.е. итератор мне никак не заменит, скажем, такую функцию, которая пишется на языке с поддержкой ленивости, но без всяких итераторов:
ВВ>Вообще вопрос можно задать так — почему итераторы не нужны в Хаскеле?
Немного не в тему. А посмотри на iteratee, если ещё не видел.
http://okmij.org/ftp/Haskell/Iteratee/DEFUN08-talk-notes.pdf
Что касается вопроса — ответили уже про ленивость.
L>Немного не в тему. А посмотри на iteratee, если ещё не видел.
L>http://okmij.org/ftp/Haskell/Iteratee/DEFUN08-talk-notes.pdf
ОК, спасибо, посмотрю.
L>Что касается вопроса — ответили уже про ленивость.
А что с ленивостью? То, что дает итератор в этом плане, прекрасно реализуется опять же без итератора через ФВП.
Кстати, если захочется, скажем, реализовать на C# свой связный список и сделать его ленивым с мемоизацией через итератор, то окажется, что стандартный итератор в этой задаче не поможет вообще никак и придется все делать ручками.
ВВ>А что с ленивостью? То, что дает итератор в этом плане, прекрасно реализуется опять же без итератора через ФВП.
Вопрос в другую сторону был поставлен. Почему в Haskell не нужны итераторы. Ответ — потому что ленивость уже решает эти задачи. Почему нужны в C# — может потому что так удобнее?
L>Вопрос в другую сторону был поставлен. Почему в Haskell не нужны итераторы. Ответ — потому что ленивость уже решает эти задачи. Почему нужны в C# — может потому что так удобнее?
У меня вообще схожие впечатления. Ленивость, необязательно неявная, если требуется non strict поведение — как при создании контейнеров, так и при вычислениях. И стандартные паттерны ФП для всего остального.
Т.е. при таком раскладе сама по себе функция-генератор оказывается как бы невостребованной.
ВВ>Т.е. при таком раскладе сама по себе функция-генератор оказывается как бы невостребованной.
В указанной ссылке как раз презентация описывающая, что аналог итератора куда лучше чем "хэндл-бэзед ИО".
L>Вопрос в другую сторону был поставлен. Почему в Haskell не нужны итераторы. Ответ — потому что ленивость уже решает эти задачи.
Не совсем приемлемый ответ. Все же списки, даже если они будут ленивыми, остаются конкретными структурами данных, а не абстракциями. Итератор шарпа же — это абстракция. Мы можем реализовать итератор поверх любой коллекции или даже без оной (пример — все тот же Range()).
L>Почему нужны в C# — может потому что так удобнее?
+1
L>>Вопрос в другую сторону был поставлен. Почему в Haskell не нужны итераторы. Ответ — потому что ленивость уже решает эти задачи.
VD>Не совсем приемлемый ответ. Все же списки, даже если они будут ленивыми, остаются конкретными структурами данных, а не абстракциями. Итератор шарпа же — это абстракция. Мы можем реализовать итератор поверх любой коллекции или даже без оной (пример — все тот же Range()).
Под ленивостью я понимал ленивость, а не ленивые списки. А под итераторами — функции с yield.
ВВ>Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В качестве наглядного примера можно взять итераторы в C# или даже более продвинутую реализацию. Неважно.
ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
ВВ>Попробую объяснить, что я имею в виду. Положим, некий код вида:
ВВ>
ВВ>Можно переписать вот так:
ВВ>
ВВ>Можно представить так и работу с бесконечными последовательностями, если действие будет описывать как Func<T,Int32,Boolean>, где второй параметр — порядковый номер элемента, а первый сам элемент. Возвращается же флажок, по которому определяются следует ли нам продолжать. Создавать такую функцию можно через простейший комбинатор вида:
ВВ>
ВВ>Т.е. с точки зрения юзабилити это, конечно, менее удобно, но вот, собственно, и все.
ВВ>В принципе у итераторов как частного случая корутин должны быть более широкие применения вроде как, но все, о чем я могу подумать, прекрасно выражается через ФВП. Причем вариант с ФВП отлично дружит с continuation-ами, с которыми те же C#-вые итераторы не дружат совсем.
ВВ>Т.е. итераторы просто сахар? Зачем они нужны?
Ну как-бы исторический фактор. Сначала были сделаны IEnumerator\IEnumerable и сахар в виде foreach для них. Потом разработчики компиляторы подумали что писать руками итераторы слишком сложно и сделали сахар в виде yield return для них.
Кроме того continuations в императивном стиле очень тяжело писать (без сахара вроде async\await).
G>Ну как-бы исторический фактор. Сначала были сделаны IEnumerator\IEnumerable и сахар в виде foreach для них. Потом разработчики компиляторы подумали что писать руками итераторы слишком сложно и сделали сахар в виде yield return для них.
G>Кроме того continuations в императивном стиле очень тяжело писать (без сахара вроде async\await).
Этот вопрос меня интересует вообще применительно к другому языку, я "перевожу стрелки" на C#, чтобы было проще обсуждать. Т.е. тяжело, неудобно писать — это все понятно. Интересно, дают ли что-либо итераторы, кроме "сахарности"?
ВВ>Этот вопрос меня интересует вообще применительно к другому языку, я "перевожу стрелки" на C#, чтобы было проще обсуждать. Т.е. тяжело, неудобно писать — это все понятно. Интересно, дают ли что-либо итераторы, кроме "сахарности"?
Если тебя интересует выражаются ли итераторы шарпа через замыкания (точнее даже будет сказать, через монады) и т.п., то таки да, выражаются. Как и наоборот. Только вот это не говорит, что итераторы шарпа чем-то плохи. Реализация на без ДКА вполне эффективна и проста.
ВВ>Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В качестве наглядного примера можно взять итераторы в C# или даже более продвинутую реализацию. Неважно.
ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
ВВ>Т.е. итераторы просто сахар? Зачем они нужны?
Поздравляю с открытием CPS
ВВ>>Т.е. итераторы просто сахар? Зачем они нужны?
KO>Поздравляю с открытием CPS
Мне кажется, его уже открыли до меня
KO>Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.
Если вопрос *только* в удобстве, почему в C# итераторы присутствуют как отдельная фича, да и компилятор там немало кода генерит? Достаточно было бы просто ввести некий сахар "для удобства" и все. Тогда бы у нас была фича, реализованная по классической схеме:
— Стандартный паттерн
— Сахарок сверху, повышающий удобство
Что, неужели итераторы в том виде, в котором они есть, появились просто по "историческим причинам"?
ВВ>Здравствуйте, k.o., Вы писали:
ВВ>>>Т.е. итераторы просто сахар? Зачем они нужны?
KO>>Поздравляю с открытием CPS
ВВ>Мне кажется, его уже открыли до меня
KO>>Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.
ВВ>Если вопрос *только* в удобстве, почему в C# итераторы присутствуют как отдельная фича, да и компилятор там немало кода генерит? Достаточно было бы просто ввести некий сахар "для удобства" и все. Тогда бы у нас была фича, реализованная по классической схеме:
ВВ>- Стандартный паттерн
ВВ>- Сахарок сверху, повышающий удобство
ВВ>Что, неужели итераторы в том виде, в котором они есть, появились просто по "историческим причинам"?
здесь
ВВ>Здравствуйте, k.o., Вы писали:
ВВ>>>Т.е. итераторы просто сахар? Зачем они нужны?
KO>>Поздравляю с открытием CPS
ВВ>Мне кажется, его уже открыли до меня
KO>>Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.
ВВ>Если вопрос *только* в удобстве, почему в C# итераторы присутствуют как отдельная фича, да и компилятор там немало кода генерит? Достаточно было бы просто ввести некий сахар "для удобства" и все. Тогда бы у нас была фича, реализованная по классической схеме:
ВВ>- Стандартный паттерн
ВВ>- Сахарок сверху, повышающий удобство
ВВ>Что, неужели итераторы в том виде, в котором они есть, появились просто по "историческим причинам"?
Вобще, ИМХО, очевидно, что итераторы, в том виде, как они есть сейчас, можно безболезненно прикрутить к большинству .NET языков (если бы их там не было). При этом использование CPS потребовало бы совершенно другой реализации и для C# и для VB.NET и для виртуальной машины, причём есть сомнения в том, что эта реализация могла бы быть не менее эффективна.
KO>>Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.
ВВ>Если вопрос *только* в удобстве, почему в C# итераторы присутствуют как отдельная фича,
Да потому что они еще и через ДКА выражаются, и еще несколькими видами. Реализовывать ДКА или CPS вручную — это не очень благодарное занятие. А в виде фичи языка все выглядит просто, императивно (последовательно) и результат получается эффективный.
ВВ>да и компилятор там немало кода генерит?
Компилятор генерирует столько сколько нужно. Реализация получается весьма эффективной. Не хуже чем при использовании CSP.
Кстати, не все такой подход избрали. В F# для аналога генераторов используются Computation Expressions, то есть по сути CPS. В C# 5 появится sync/await. Реализация будет опять же на основе ДКА (точнее даже на основе итераторов). В F# и Nemerle есть аналогичная фича реализованная на базе Computation Expressions. Однако реализация на базе ДКА будет ничем не хуже. Так что возможно в немреле и ее реализуем.
ВВ>Достаточно было бы просто ввести некий сахар "для удобства" и все. Тогда бы у нас была фича, реализованная по классической схеме:
ВВ>- Стандартный паттерн
ВВ>- Сахарок сверху, повышающий удобство
Ну, вот Computation Expressions и есть такой подход. Наверно и с итераторами и sync/await можно было так поступить. Но Хейльсберг все в язык тащит.
Но если тогда уж задуматься, то лучшим выходом было бы просто ввести макросы, а уже все подобные детали реализовать на их основе.
VD>Кстати, не все такой подход избрали. В F# для аналога генераторов используются Computation Expressions, то есть по сути CPS. В C# 5 появится sync/await. Реализация будет опять же на основе ДКА (точнее даже на основе итераторов). В F# и Nemerle есть аналогичная фича реализованная на базе Computation Expressions. Однако реализация на базе ДКА будет ничем не хуже. Так что возможно в немреле и ее реализуем.
В F# есть "yield!". Как он будет работать в варианте с ДКА?
ВВ>В F# есть "yield!". Как он будет работать в варианте с ДКА?
Описание того как sync/await сделаны в шарпе доступны в Интернете. Да поможет тебе гугль. Ну, а то что названия не совпадают, ты уже извини.
ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
ВВ> for (var i = start; i < e; i++)
ВВ> fun(i);
Тебя, видимо, слишком уводит в сторону тут специфика C#. "В общем и целом" переменная i в данном случае — итератор. У тебя в примере он — целое число, но представь себе что-то более сложное: например, перебор элементов дерева, или ответа на SQL запрос. В этом случае ты уже не сможешь написать такой for в простом виде — и вынужден будешь или писать с учётом внутренностей реализации, или таки делать итератор. А будет этот итератор сделан через yield, через объект с методом next() или как-то ещё — уже неважно.
N>Тебя, видимо, слишком уводит в сторону тут специфика C#. "В общем и целом" переменная i в данном случае — итератор. У тебя в примере он — целое число, но представь себе что-то более сложное: например, перебор элементов дерева, или ответа на SQL запрос. В этом случае ты уже не сможешь написать такой for в простом виде — и вынужден будешь или писать с учётом внутренностей реализации, или таки делать итератор. А будет этот итератор сделан через yield, через объект с методом next() или как-то ещё — уже неважно.
Я вообще спрашиваю о генераторах, если такой термин понятнее/привычнее. В дотнете IEnumerable выполняет две задачи — абстракция для перебора элементов (тут ОК, и эта тема вообще отдельная) и интерфейс функции-генератора (т.е. функции, которая умеет возвращать свое значение несколько раз и сохраняет весь свой стек между вызовами).
ВВ>Здравствуйте, netch80, Вы писали:
N>>Тебя, видимо, слишком уводит в сторону тут специфика C#. "В общем и целом" переменная i в данном случае — итератор. У тебя в примере он — целое число, но представь себе что-то более сложное: например, перебор элементов дерева, или ответа на SQL запрос. В этом случае ты уже не сможешь написать такой for в простом виде — и вынужден будешь или писать с учётом внутренностей реализации, или таки делать итератор. А будет этот итератор сделан через yield, через объект с методом next() или как-то ещё — уже неважно.
ВВ>Я вообще спрашиваю о генераторах, если такой термин понятнее/привычнее. В дотнете IEnumerable выполняет две задачи — абстракция для перебора элементов (тут ОК, и эта тема вообще отдельная) и интерфейс функции-генератора (т.е. функции, которая умеет возвращать свое значение несколько раз и сохраняет весь свой стек между вызовами).
Так тебя интересует полезность конструкции генератора именно в виде функции с yield? Если так — то вопрос сводится к тому, что другой дизайн требует реализации (хоть обычно и крошечной, но) FSM, а это для многих уже высший пилотаж.
Да, тогда можно форму с yield считать сахаром.
N>Так тебя интересует полезность конструкции генератора именно в виде функции с yield? Если так — то вопрос сводится к тому, что другой дизайн требует реализации (хоть обычно и крошечной, но) FSM, а это для многих уже высший пилотаж.
N>Да, тогда можно форму с yield считать сахаром.
FSM не нужен, если есть первоклассные функции. А yield — это может и сахар, но никак не сахар для ФВП. Т.е. то, что дает yield, реализуется через ФВП, но сам yield сделан иначе. Мне интересно — почему.
И вопрос, наверное, следовало бы поставить как: "Почему нужны итераторы" — и вот тут есть довольно резонный ответ — потому что, как правило, ООП-языки не держат функцию за первоклассный объект. Приходится выдумывать в рамках ООП костылёк. Я совершенно не удивлюсь, если мне скажут, что Visitor не нужен, потому что есть map()
W>Итератор (курсор) — идиома ООП.
W>И вопрос, наверное, следовало бы поставить как: "Почему нужны итераторы" — и вот тут есть довольно резонный ответ — потому что, как правило, ООП-языки не держат функцию за первоклассный объект. Приходится выдумывать в рамках ООП костылёк. Я совершенно не удивлюсь, если мне скажут, что Visitor не нужен, потому что есть map()
А я не удивлюсь если тебе замеят, что шаблон Посетитель и функция map решают разные задачи.
VD>А я не удивлюсь если тебе замеят, что шаблон Посетитель и функция map решают разные задачи.
В любом случае map — частный случай fold с одной стороны, а с другой — "Visitors — Equational functions. Frequently foldable"
Так что абсолютно неправым я быть не могу
А что такое порядковый номер? Есть масса контейнеров, где он невозможен или бессмысленен. А так можно писать алгоритм, который слабо зависит от контейнера
, Вы писали:
ВВ>Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В качестве наглядного примера можно взять итераторы в C# или даже более продвинутую реализацию. Неважно.
ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
ВВ>Попробую объяснить, что я имею в виду. Положим, некий код вида:
ВВ>
ВВ>Можно переписать вот так:
ВВ>
ВВ>Можно представить так и работу с бесконечными последовательностями, если действие будет описывать как Func<T,Int32,Boolean>, где второй параметр — порядковый номер элемента, а первый сам элемент. Возвращается же флажок, по которому определяются следует ли нам продолжать. Создавать такую функцию можно через простейший комбинатор вида:
ВВ>
ВВ>Т.е. с точки зрения юзабилити это, конечно, менее удобно, но вот, собственно, и все.
ВВ>В принципе у итераторов как частного случая корутин должны быть более широкие применения вроде как, но все, о чем я могу подумать, прекрасно выражается через ФВП. Причем вариант с ФВП отлично дружит с continuation-ами, с которыми те же C#-вые итераторы не дружат совсем.
ВВ>Т.е. итераторы просто сахар? Зачем они нужны?
PP4>Здравствуйте, Воронков Василий
PP4>А что такое порядковый номер? Есть масса контейнеров, где он невозможен или бессмысленен. А так можно писать алгоритм, который слабо зависит от контейнера
Например, как? Все способы, предлагаемые линком, чтобы работать с циклическим итератором вида
это именно "отщепить" кусочек. Причем кусочек "в цифрах". Будь то First или Take. Вы можете еще раскрутить его через foreach, а в нужный момент дернуть break — но для всего, что можно перебрать через foreach порядковый номер элемента вполне имеет смысл хотя бы рамках этого перебора.
Т.е. линк со своими итераторами и сам никаких особых техник работы с бесконечными последовательностями не предлагает.
PP4>А что такое порядковый номер? Есть масса контейнеров, где он невозможен или бессмысленен. А так можно писать алгоритм, который слабо зависит от контейнера
Это у него такой пунктик. Он весьма странно понимает абстракции. Считает что массив и список это абстракции, а следовательно можно смело пользоваться индексом для выражения порядка элементов списка.
ВВ>Т.е. итераторы просто сахар? Зачем они нужны?
По сути да сахар, но позволяющий для императивных языков довольно красиво и компактно делать то что в функциональщине
делается через ФВП, например в питоне 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>однородно используя тот же набор ФВП, что и для списков и плюс изолировать в них грязь, как пример BatEnum для
FR>OCaml — http://thelema.github.com/batteries-included/hdoc/BatEnum.html
Да, вот только "перечислитель" в стиле IEnumerator, подобный которому предлагается и в BatEnum, как я понимаю, это достаточно императивная по своей природе штука. Он предлагает обобщенный способ для работы с коллекциями, но *какой* способ? Скажем, вместо красивого рекурсивного:
Я что буду писать? Это если мне захочется сделать свою функцию, которая что-то делает с энумератором.
Альтернативой тут может быть подход к абстракции для контейнера с другого конца — вот тот же Foldable.
ВВ>Да, вот только "перечислитель" в стиле IEnumerator, подобный которому предлагается и в BatEnum, как я понимаю, это достаточно императивная по своей природе штука.
Да галимый императив в реализации.
Но интерфейс практически чисто функциональный.
ВВ>Он предлагает обобщенный способ для работы с коллекциями, но *какой* способ?
Очень простой работать через базовые ФВП.
ВВ>Скажем, вместо красивого рекурсивного:
ВВ>
ВВ>Я что буду писать? Это если мне захочется сделать свою функцию, которая что-то делает с энумератором.
В случае fold ничего ни будешь, он уже есть, также как и все базовые ФВП.
В этом-то и суть что есть база в виде ФВП и все остальное выражается через нее. Плюс list comprehensions в
батарейках по умолчанию дает Enum.
ВВ>Альтернативой тут может быть подход к абстракции для контейнера с другого конца — вот тот же Foldable.
Ну одно другому не мешает http://thelema.github.com/batteries-included/hdoc/BatEnum.WithMonad.html
FR>Да галимый императив в реализации.
FR>Но интерфейс практически чисто функциональный.
Интерфейс — да. Я имею в виду — а как мне писать код, если я решу написать свою функцию, какой-нибудь особенный "fold", работающий с этим самым энумератором. Как я понимаю, через циклы или что-то в этом роде?
FR>В случае fold ничего ни будешь, он уже есть, также как и все базовые ФВП.
FR>В этом-то и суть что есть база в виде ФВП и все остальное выражается через нее.
Получается, что это функциональная обвертка над императивной штукой
FR>Плюс list comprehensions в батарейках по умолчанию дает Enum.
Т.е. list comprehension фактически становится enum comprehension?
ВВ>Интерфейс — да. Я имею в виду — а как мне писать код, если я решу написать свою функцию, какой-нибудь особенный "fold", работающий с этим самым энумератором. Как я понимаю, через циклы или что-то в этом роде?
Зачем, кошерный способ писать используя базовые ФВП.
Если это не подходит есть низкоуровневые http://thelema.github.com/batteries-included/hdoc/BatEnum.html#6_Usefulfunctions
функции, конечно они по сути императивы, но циклов и изменяемых переменных для работы не требуют.
FR>>В случае fold ничего ни будешь, он уже есть, также как и все базовые ФВП.
FR>>В этом-то и суть что есть база в виде ФВП и все остальное выражается через нее.
ВВ>Получается, что это функциональная обвертка над императивной штукой
Да.
FR>>Плюс list comprehensions в батарейках по умолчанию дает Enum.
ВВ>Т.е. list comprehension фактически становится enum comprehension?
Ну там возможно задавать что получишь на выходе, по умолчанию да enum, но
можно и списки и даже строки и массивы:
ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
В энергичных языках есть важная разница: кто кого вызывает и контролирует — основной код просит у итератора значение или итератор (генератор) пихает очередное значение основному коду. Простой пример: у нас есть 100 итераторов, представляющих строки в разных файлах (неизвестной длины), и мы хотим вывести все первые строки, потом все вторые, все третьи и т.д. С итераторами а-ля C# или ленивыми списками это делается очень просто. А как это будет выглядеть с генераторами/ФВП?
> Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В
> качестве наглядного примера можно взять итераторы в C# или даже более
> продвинутую реализацию. Неважно.
Вообще итераторы не нужны. Но придуманы они были для того, чтобы отделить
алгоритмы обработки коллекций от способов доступа к этим коллекциям,
чтобы алгоритмы были бы универсальными.
ВВ>Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик.
Вот это как-то странно.
ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
Ну, во-первых, можно даже сделать более сильное утверждение: итераторы изоморфны событиям.
В твоём примере IEnumerable<int> лёгким движением руки превращается в Action<int>. Это — частный случай того, о чём много и убедительно пишет Барт.
Понятно, что сразу получаем обратный вопрос: а зачем, собственно, нужны события и возможность передавать свою функцию куда-то туда, откуда можно брать и вычитывать значения?
Ответ, конечно же, очевиден: удобным бывает разное представление одного и того же алгоритма. Кроме этого, надо понимать, что дотнет принципиально многоязыковая среда. Потреблять итератор можно и из языка, в котором нет замыканий; потреблять твой вариант — крайне тяжело.
Так что, таки да: "с точки зрения юзабилити это, конечно, менее удобно, но вот, собственно, и все."
Ну и, конечно же, как всегда, рекомендую почитать Липперта на тему применибельности CPS в C#.
S>Ответ, конечно же, очевиден: удобным бывает разное представление одного и того же алгоритма.
Зачем?
S>Кроме этого, надо понимать, что дотнет принципиально многоязыковая среда. Потреблять итератор можно и из языка, в котором нет замыканий; потреблять твой вариант — крайне тяжело.
Язык, в котором нет функций-генераторов, сможет только вызвать такую функцию, и все. Причем для удобной работы с этими функциями все равно должна быть некоторая поддержка со стороны языка, иначе "крайне тяжело" будет работать и с обычными итераторами. Яркий пример — МС++, где даже for each для IEnumerable не было.
Первоклассные же функции имитируются в C# через делегаты, ибо только делегат можно передать в другую функцию. Делегат же — это уже не фича C#. Таким образом, ФВП, написанные на C#, вполне юзабельны и из других .NET-языков — насколько "тяжело" будет с ними работать зависит опять же от того, поддерживает ли язык специальный сахар для делегатов, умеет ли его компилятор создавать замыкания и пр.
Я не вижу здесь какой-то кардинальной разницы.
S>Так что, таки да: "с точки зрения юзабилити это, конечно, менее удобно, но вот, собственно, и все."
S>Ну и, конечно же, как всегда, рекомендую почитать Липперта на тему применибельности CPS в C#.
Меня C# вообще мало интересует. Применимо там CPS или неприменимо, насколько оно совместимо с C# программистами и проч. Мне как раз интересно, были ли другие причины, кроме "в .NET есть и другие языки" или "CPS непонятен людям" сделать все так, а не иначе. Липперт как раз на этот вопрос не отвечает.
ВВ>Меня C# вообще мало интересует. Применимо там CPS или неприменимо, насколько оно совместимо с C# программистами и проч.
Странный вы человек, сначала спрашиваете "нафига итераторы?", а на "в шарпе ими удобно пользоваться" отвечаете, что шарп вас не интересует
ВВ>Мне как раз интересно, были ли другие причины, кроме "в .NET есть и другие языки" или "CPS непонятен людям" сделать все так, а не иначе. Липперт как раз на этот вопрос не отвечает.
Отвечает, и неоднократно. Шарп — мейнстрим; соображения практического удобства ценятся на голову выше, чем абстрактная чистота. Для повышения ЧСВ есть куча других языков и платформ, к чему эти регулярные крестовые походы на <подставить фичу>?
ВВ>>Мне как раз интересно, были ли другие причины, кроме "в .NET есть и другие языки" или "CPS непонятен людям" сделать все так, а не иначе. Липперт как раз на этот вопрос не отвечает.
S>Отвечает, и неоднократно.
Что отвечает? "Шарп — мейнстрим" это не ответ. Не тот ответ, который мне интересен.
S>Шарп — мейнстрим; соображения практического удобства ценятся на голову выше, чем абстрактная чистота. Для повышения ЧСВ есть куча других языков и платформ, к чему эти регулярные крестовые походы на <подставить фичу>?
Не занимаюсь я никакими крестовыми походами на <подставить фичу>. Говоря, что шарп меня интересует, я имею в виду, что не хочу склонять обсуждение темы к тому, насколько оправдано существование генераторов наряду с замыканиями конкретно в C#. Хотя бы потому что ответы будут "в стиле Липперта". Это не значит, что ответы плохие. Меня просто интересует другое.
Будет проще, если я спрошу, оправдано ли в неком абстрактном языке существование наряду с замыканиями функций-генераторов, какие задачи они могут решать и почему эти задачи не могут быть решены через ФВП с таким же удобством. Неужели обсуждать сферических коней в вакууме проще?
ВВ>Что отвечает? "Шарп — мейнстрим" это не ответ. Не тот ответ, который мне интересен.
Так вам уже по-всякому ответили. Делегаты рулят для простого расширения поведения. Итераторы — для работы с последовательностями. То, что одно можно выразить через другое, не значит что остаться должен кто-то один.
Если убирать итераторы — что вы будете делать с foreach? Как вы реализуете linq? Представляете примерный объём работы по переделке рантайма/BCL/документации/инструментария? Нафига?
ВВ>Будет проще, если я спрошу, оправдано ли в неком абстрактном языке существование наряду с замыканиями функций-генераторов, какие задачи они могут решать и почему эти задачи не могут быть решены через ФВП с таким же удобством. Неужели обсуждать сферических коней в вакууме проще?
Да, потому что можно забить на то, насколько таким языком будет удобно пользоваться, какая у него будет популярность и какие шансы на реализацию на нём платформы, аналогичной фреймворку.
Поймите, никого не колышет язык сам по себе, побеждает связка "язык+библиотеки+инструментарий+документация+community". Вы можете сделать сколько угодно сверхфичастых сфероязыков, но пока на них не будет удобно как клепать формочку для студенческой лабораторной, так и писать софт уровня студии — ваш гипотетический язык так и останется игрушкой для гиков.
ВВ>>Что отвечает? "Шарп — мейнстрим" это не ответ. Не тот ответ, который мне интересен.
S>Так вам уже по-всякому ответили. Делегаты рулят для простого расширения поведения. Итераторы — для работы с последовательностями. То, что одно можно выразить через другое, не значит что остаться должен кто-то один.
S>Если убирать итераторы — что вы будете делать с foreach? Как вы реализуете linq? Представляете примерный объём работы по переделке рантайма/BCL/документации/инструментария? Нафига?
Причем тут переделка BCL?
S>Поймите, никого не колышет язык сам по себе, побеждает связка "язык+библиотеки+инструментарий+документация+community". Вы можете сделать сколько угодно сверхфичастых сфероязыков, но пока на них не будет удобно как клепать формочку для студенческой лабораторной, так и писать софт уровня студии — ваш гипотетический язык так и останется игрушкой для гиков.
Форум под названием "Философия программирования" должен быть посвящен по-вашему обсуждению клепания формочек?
Мне одно непонятно — если единственное, что вы можете ответить, это апеллировать к "практичности" и мейнстриму, зачем вообще что-то отвечать? Мне обязательно каждый раз оправдываться за то, что я наступил кому-то на любимую фичу?
S>>Если убирать итераторы — что вы будете делать с foreach? Как вы реализуете linq? Представляете примерный объём работы по переделке рантайма/BCL/документации/инструментария? Нафига?
ВВ>Причем тут переделка BCL?
[К.О]
1. Множество методов из BCL принимают/возвращают IEnumerable
2. Придётся протаскивать везде ФВП, да ещё так, чтобы они по производительности не отстали от state-машниы, в которую разворачивается итератор.
[/К.О]
S>>Поймите, никого не колышет язык сам по себе, побеждает связка "язык+библиотеки+инструментарий+документация+community". Вы можете сделать сколько угодно сверхфичастых сфероязыков, но пока на них не будет удобно как клепать формочку для студенческой лабораторной, так и писать софт уровня студии — ваш гипотетический язык так и останется игрушкой для гиков.
ВВ>Форум под названием "Философия программирования" должен быть посвящен по-вашему обсуждению клепания формочек?
Нет, форум под названием "Философия программирования" должен быть как можно дальше оторван от жизни, чтобы красоту мыслей не плющило суровой реальностью.
ВВ>Мне одно непонятно — если единственное, что вы можете ответить, это апеллировать к "практичности" и мейнстриму, зачем вообще что-то отвечать? Мне обязательно каждый раз оправдываться за то, что я наступил кому-то на любимую фичу?
Забавная параллель:
ВВ>Зачем?
Очевидно, для компактности и воспринимаемости записанного кода.
S>>Кроме этого, надо понимать, что дотнет принципиально многоязыковая среда. Потреблять итератор можно и из языка, в котором нет замыканий; потреблять твой вариант — крайне тяжело.
ВВ>Язык, в котором нет функций-генераторов, сможет только вызвать такую функцию, и все. Причем для удобной работы с этими функциями все равно должна быть некоторая поддержка со стороны языка, иначе "крайне тяжело" будет работать и с обычными итераторами. Яркий пример — МС++, где даже for each для IEnumerable не было.
ВВ>Первоклассные же функции имитируются в C# через делегаты, ибо только делегат можно передать в другую функцию. Делегат же — это уже не фича C#. Таким образом, ФВП, написанные на C#, вполне юзабельны и из других .NET-языков — насколько "тяжело" будет с ними работать зависит опять же от того, поддерживает ли язык специальный сахар для делегатов, умеет ли его компилятор создавать замыкания и пр.
ВВ>Я не вижу здесь какой-то кардинальной разницы.
Сравним два фрагмента кода на воображаемом языке:
1.
2.
В данном случае, разница мне кажется очевидной.
ВВ>Меня C# вообще мало интересует. Применимо там CPS или неприменимо, насколько оно совместимо с C# программистами и проч. Мне как раз интересно, были ли другие причины, кроме "в .NET есть и другие языки" или "CPS непонятен людям" сделать все так, а не иначе. Липперт как раз на этот вопрос не отвечает.
По-моему, вы капризничаете. Фичи не существуют "в воздухе", сами по себе. Они существуют в контексте некоторого фреймворка. В данном конкретном фреймворке есть данности
— существующий интерфейс IEnumerable
— паттерны по его поддержке
— ограничения на то, что можно/нельзя использовать в замыкании
С учётом этих данностей и было принято решение. Как я уже сказал, глобально — никаких причин не делать поддержку "обратных вызовов" нету.
ВВ>>Зачем?
S>Очевидно, для компактности и воспринимаемости записанного кода.
Это причина, по которой в язык добавляют сахар. Но это не причина для добавления новых фич.
Зайдем с другой стороны. Предположим, что у нас есть воображаемый язык Blab. В языке Blab есть первоклассные функции и замыкания. Язык Blab существует сам по себе, без всяких дотнетов — более того, у него вообще свой собственный бэкенд, спроектированный специально для Blab-а.
Также предположим, что Blab — это функциональный язык.
Blab-еры как-то прознали о том, что существует такая хитрая фича — функции, которые могут возвращать значение несколько раз и к тому же умеют сохранять свое состояние между вызовами. На первый взгляд Блаберам кажется, что фича эта странная и стремная: какие-то побочные эффекты непонятные, вызов и использование таких функций будет сильно отличаться от обычных, с рекурсией внутри них тоже косяки, потом опять же на первый взгляд им кажется, что ничего эта фича в сущности не дает-то. Query Comprehension? Да это же простые комбинаторы. Отложенные вычисления? Так у Блабером для этого санки есть. А на санках и ленивые списки строятся. Через такие функции можно получить универсальный интерфейс для работы с контейнерами? Ну во-первых есть Foldable, а во-вторых — получить-то можно, а как работать с этими контейнерами придется, в императивном стиле? А в Блабе может и вообще циклов не быть.
Однако вот друзья-товарищи из Питона и Руби говорят, что фича мировая. Какие причины эти самые товарищи могут привести, чтобы убедить блаберов, что генераторы их языку, несмотря на вышесказанное, все же не помешают?
ВВ>Однако вот друзья-товарищи из Питона и Руби говорят, что фича мировая. Какие причины эти самые товарищи могут привести, чтобы убедить блаберов, что генераторы их языку, несмотря на вышесказанное, все же не помешают?
Никакие. Потому что фичи добавляются не сами по себе, а в комплексе, чтобы сделать язык удобным для решения определённого класса задач. Выбор набора фич определяется исключительно религиозными предпочтениями + (если повезёт) целевой аудиторией языка.
Как правило, дизайн языка с самого начала включает в себя законченный набор, покрывающий основные фичи языка с минимальным разнообразием. Этот же набор используют базовые библиотеки + сторонние фреймворки.
Аналогичный подход применяется (в идеале) и при дальнейшем развитии языка, иначе он моментально превратится в помойку. И, увы, добавить что-то новое, чтобы оно не выглядело чужеродным обвесом, но и не ломало совместимость с каждым релизом всё сложнее и сложнее.
Так что пока блаберам не потребуется добавить ряд фич, которые удобно решаются на итераторах (и оочень неудобно решать ч/з ФВП), итераторов в блабе не будет. По той же причине в шарпе нет ФВП в чистом виде.
ВВ>Blab-еры как-то прознали о том, что существует такая хитрая фича — функции, которые могут возвращать значение несколько раз и к тому же умеют сохранять свое состояние между вызовами.
А можно мне показать описание вот этой хитрой фичи? Потому что я знаком только с дотнетной реализацией итераторов, так вот она ведёт себя вовсе не так, как тут описано. Питоновая реализация, судя по описанию, тоже совсем не то.
ВВ> На первый взгляд Блаберам кажется, что фича эта странная и стремная: какие-то побочные эффекты непонятные, вызов и использование таких функций будет сильно отличаться от обычных, с рекурсией внутри них тоже косяки, потом опять же на первый взгляд им кажется, что ничего эта фича в сущности не дает-то.
Ну, если делать такую странную и стрёмную фичу, то конечно же непонятно, в чём её смысл.
Скорее всего, в Блабе эту фичу сделать и вовсе не получится. Он же функциональный, поэтому в нём не будет изменяемого итератора. Зато концепция ленивого списка может быть встроена в него с самого начала.
ВВ>>Blab-еры как-то прознали о том, что существует такая хитрая фича — функции, которые могут возвращать значение несколько раз и к тому же умеют сохранять свое состояние между вызовами.
S>А можно мне показать описание вот этой хитрой фичи? Потому что я знаком только с дотнетной реализацией итераторов, так вот она ведёт себя вовсе не так, как тут описано. Питоновая реализация, судя по описанию, тоже совсем не то.
Со стороны пользователя практически так она себя и ведет. Только интерфейс вызова выглядит как (Current/MoveNext) и представлен в виде объекта, который, собственно, и инкапсулирует в себе состояние функции, ее как бы стек. В Питоне по-моему несколько иначе. И завершение исполнения там сигнализируется выбросом исключения.
S>Ну, если делать такую странную и стрёмную фичу, то конечно же непонятно, в чём её смысл.
S>Скорее всего, в Блабе эту фичу сделать и вовсе не получится. Он же функциональный, поэтому в нём не будет изменяемого итератора.
Зависит. В Ocaml-е же есть BatEnum. Т.е. что-то же заставило OCaml-овцев, в отличие от Блаберов, таки прийти к выводу, что итераторы это есть хорошо, несмотря на то, что в языке вроде как нет и недостатка в фичах из Блаба.
S>Зато концепция ленивого списка может быть встроена в него с самого начала.
Ленивый список — это просто x :: thunk, т.е. это и концепцией-то назвать сложно, можно сказать, особенность самой структуры данных.
ВВ>Со стороны пользователя практически так она себя и ведет. Только интерфейс вызова выглядит как (Current/MoveNext) и представлен в виде объекта, который, собственно, и инкапсулирует в себе состояние функции, ее как бы стек. В Питоне по-моему несколько иначе. И завершение исполнения там сигнализируется выбросом исключения.
Нет. Со стороны пользователя эта фича ведёт себя совершенно иначе. Есть foreach, и он магическим образом пользуется Current/MoveNext/Dispose. Пользователю всё равно, что там унутре за неонка. К примеру, C++ итераторы устроены точно так же, несмотря на отстутствие замыканий и чего бы то ни было другого.
Резюме: функция со стороны пользователя никоим образом не умеет "возвращать значение несколько раз" или "сохранять своё состояние". Функция возвращает значение ровно однажды, это значение типа IEnumerator<T>. Насколько я успел понять диагональным методом, в питоне всё строго так же.
Реализация внутренностей c yield return строго ортогональна потреблению с foreach.
ВВ>Зависит. В Ocaml-е же есть BatEnum. Т.е. что-то же заставило OCaml-овцев, в отличие от Блаберов, таки прийти к выводу, что итераторы это есть хорошо, несмотря на то, что в языке вроде как нет и недостатка в фичах из Блаба.
Ну, тогда, наверное, надо спросить окамловцев. Я с BatEnum совершенно незнаком — почему и прошу показать мне описание. Задавать вопрос про BatEnum в терминах шарпа я считаю несколько экстравагантным.
S>>Зато концепция ленивого списка может быть встроена в него с самого начала.
ВВ>Ленивый список — это просто x :: thunk, т.е. это и концепцией-то назвать сложно, можно сказать, особенность самой структуры данных.
Ну так концепция ленивого списка == IEnumerable. Если она уже есть, то ещё один IEnumerable не нужен.
ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
Гм... точно ? т.е. быструю сортировку успешно выполнить через замыкания ?
ВВ>>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
M>Гм... точно ? т.е. быструю сортировку успешно выполнить через замыкания ?
А что мешает-то?
ВВ>Здравствуйте, minorlogic, Вы писали:
M>>Гм... точно ? т.е. быструю сортировку успешно выполнить через замыкания ?
ВВ>А что мешает-то?
Возможно я заблуждаюсь , но не видел красивой и эфективной сортировки без итераторов. Дайте линк.
ВВ>>А что мешает-то?
M>Возможно я заблуждаюсь , но не видел красивой и эфективной сортировки без итераторов. Дайте линк.
Вы мне сначала покажите то, что называете сортировкой на итераторах.
ВВ>Здравствуйте, minorlogic, Вы писали:
ВВ>>>А что мешает-то?
M>>Возможно я заблуждаюсь , но не видел красивой и эфективной сортировки без итераторов. Дайте линк.
ВВ>Вы мне сначала покажите то, что называете сортировкой на итераторах.
http://en.wikipedia.org/wiki/Quicksort
ВВ>>Вы мне сначала покажите то, что называете сортировкой на итераторах.
M>http://en.wikipedia.org/wiki/Quicksort
http://en.wikipedia.org/wiki/Recursion
M>>Гм... точно ? т.е. быструю сортировку успешно выполнить через замыкания ?
ВВ>А что мешает-то?
Многие помнят, как просто выражается квиксорт ("ненастоящий") на Хаскеле со списками: фильтруем, вызываем рекурсивно, соединяем.
А как это будет выглядеть с ФВП (без итераторов и ленивых списков) Когда есть функция, куда можно передать свою, и она получит все значения последовательности.
И еще попрошу все же показать, как будет выглядеть мой пример с последовательным выводом строк из сотни файлов.
DM>Многие помнят, как просто выражается квиксорт ("ненастоящий") на Хаскеле со списками: фильтруем, вызываем рекурсивно, соединяем.
DM>А как это будет выглядеть с ФВП (без итераторов и ленивых списков) Когда есть функция, куда можно передать свою, и она получит все значения последовательности.
Во-первых, откуда вы взяли ограничение "без ленивых списков"? Они-то как под нож попали? А на ленивых списках тот же Линк имитируется один в один.
Во-вторых, сортировка в Линке построена в сущности на континуэшине. Там компаратор работает примерно так:
И сдается мне, что итераторы тут играют сильно побочную роль.
DM>И еще попрошу все же показать, как будет выглядеть мой пример с последовательным выводом строк из сотни файлов.
Было бы неплохо, если вы бы свой пример тоже показали. Я не очень понимаю, что имеется в виду под "последовательным выводом строк из сотни файлов".
ВВ>Здравствуйте, D. Mon, Вы писали:
DM>>Многие помнят, как просто выражается квиксорт ("ненастоящий") на Хаскеле со списками: фильтруем, вызываем рекурсивно, соединяем.
DM>>А как это будет выглядеть с ФВП (без итераторов и ленивых списков) Когда есть функция, куда можно передать свою, и она получит все значения последовательности.
ВВ>Во-первых, откуда вы взяли ограничение "без ленивых списков"? Они-то как под нож попали? А на ленивых списках тот же Линк имитируется один в один.
С ленивыми списками разница между итераторами и генераторами размывается. Я специально выше говорил про энергичные языки.
ВВ>Во-вторых, сортировка в Линке построена в сущности на континуэшине. Там компаратор работает примерно так:
Кажется, это сравнение двух последовательностей. К сортировке оно не относится.
ВВ>И сдается мне, что итераторы тут играют сильно побочную роль.
Речь о том, что как в хаскеле мы можем просто фильтровать и соединять списки, получая квиксорт в три строчки, так и с итераторами — мы можем их фильтровать и соединять, как списки.
DM>>И еще попрошу все же показать, как будет выглядеть мой пример с последовательным выводом строк из сотни файлов.
ВВ>Было бы неплохо, если вы бы свой пример тоже показали. Я не очень понимаю, что имеется в виду под "последовательным выводом строк из сотни файлов".
Абстрагируясь от работы с файлами, суть такова. Пусть у нас есть несколько итераторов, выдающих последовательности строк "fileN lineM". Нужно вывести сначала все первые строчки, затем вторые и т.д.
Простите за мой французский, но я напишу на Окамле
Теперь прошу аналог с ФВП и, если угодно, CPS.
DM>С ленивыми списками разница между итераторами и генераторами размывается. Я специально выше говорил про энергичные языки.
Стоп. Генератор — это вполне конкретная фишка. Это функция, которая может возвращать свое значение несколько раз и может сохранять свое состояние между вызовами. Итератор — ну это просто паттерн.
ВВ>>Во-вторых, сортировка в Линке построена в сущности на континуэшине. Там компаратор работает примерно так:
DM>Кажется, это сравнение двух последовательностей. К сортировке оно не относится.
Это не сравнение двух последовательностей, это генерация ключей, по которым производится сортировка. Именно так и работает "цепочечная" сортировка в Linq вида OrderBy(...).ThenBy(...).ThenBy(...). Т.е. построена она на CPS по сути.
ВВ>>Было бы неплохо, если вы бы свой пример тоже показали. Я не очень понимаю, что имеется в виду под "последовательным выводом строк из сотни файлов".
DM>Абстрагируясь от работы с файлами, суть такова. Пусть у нас есть несколько итераторов, выдающих последовательности строк "fileN lineM". Нужно вывести сначала все первые строчки, затем вторые и т.д.
DM>Простите за мой французский, но я напишу на Окамле
DM>Теперь прошу аналог с ФВП и, если угодно, CPS.
Ну например, мне тут по соседству приводили пример реализации паттерна Iteratee на Хаскеле.
http://okmij.org/ftp/Haskell/Iteratee/DEFUN08-talk-notes.pdf
Вкратце:
Вообще при работе с файлами у нас и так есть некий итератор, поток, stream. Я могу написать вариант через CPS, но все равно я для начала должен исходить из того, откуда именно происходит чтение.
Пока получается, что ваша задача звучит так: у вас есть итератор, попробуйте с ним работать без итератора.
DM>>Абстрагируясь от работы с файлами, суть такова. Пусть у нас есть несколько итераторов, выдающих последовательности строк "fileN lineM". Нужно вывести сначала все первые строчки, затем вторые и т.д.
ВВ>Ну например, мне тут по соседству приводили пример реализации паттерна Iteratee на Хаскеле.
ВВ>http://okmij.org/ftp/Haskell/Iteratee/DEFUN08-talk-notes.pdf
ВВ>Вкратце:
Ну вот и напишите, как пройтись параллельно по нескольким итераторам. (не в смысле многоядерности, а в смысле порядка выводимых элементов)
ВВ>Вообще при работе с файлами у нас и так есть некий итератор, поток, stream.
Да, есть, в окамле тоже. Это несущественно. Покажите, как с CPS по нескольким файлам пройтись.
ВВ>Стоп. Генератор — это вполне конкретная фишка. Это функция, которая может возвращать свое значение несколько раз и может сохранять свое состояние между вызовами. Итератор — ну это просто паттерн.
Прошу прощения, я могу быть неточным в терминах. Под итератором я имел в виду IEnumerable из C# и Enum из Окамла — пассивный объект, выдающий элементы по запросу. Под генератором я имел в виду описанный в первом посте Func<T,Int32,Boolean> или его аналог в Руби (там, где for по последовательности превращается в вызов метода с передачей тела цикла в виде ФВП). Именно между ними я вижу принципиальную разницу, и мне интересно, как первое выразить через второе.
DM>Здравствуйте, Воронков Василий, Вы писали:
ВВ>>Здравствуйте, D. Mon, Вы писали:
DM>>>Многие помнят, как просто выражается квиксорт ("ненастоящий") на Хаскеле со списками: фильтруем, вызываем рекурсивно, соединяем.
DM>>>А как это будет выглядеть с ФВП (без итераторов и ленивых списков) Когда есть функция, куда можно передать свою, и она получит все значения последовательности.
ВВ>>Во-первых, откуда вы взяли ограничение "без ленивых списков"? Они-то как под нож попали? А на ленивых списках тот же Линк имитируется один в один.
DM>С ленивыми списками разница между итераторами и генераторами размывается. Я специально выше говорил про энергичные языки.
ВВ>>Во-вторых, сортировка в Линке построена в сущности на континуэшине. Там компаратор работает примерно так:
DM>Кажется, это сравнение двух последовательностей. К сортировке оно не относится.
ВВ>>И сдается мне, что итераторы тут играют сильно побочную роль.
DM>Речь о том, что как в хаскеле мы можем просто фильтровать и соединять списки, получая квиксорт в три строчки, так и с итераторами — мы можем их фильтровать и соединять, как списки.
DM>>>И еще попрошу все же показать, как будет выглядеть мой пример с последовательным выводом строк из сотни файлов.
ВВ>>Было бы неплохо, если вы бы свой пример тоже показали. Я не очень понимаю, что имеется в виду под "последовательным выводом строк из сотни файлов".
DM>Абстрагируясь от работы с файлами, суть такова. Пусть у нас есть несколько итераторов, выдающих последовательности строк "fileN lineM". Нужно вывести сначала все первые строчки, затем вторые и т.д.
DM>Простите за мой французский, но я напишу на Окамле
DM>
DM>Теперь прошу аналог с ФВП и, если угодно, CPS.
Кстати, если считать, что на входе у нас ['a]], т.е. структура данных, про которую мы знаем, как ее можно перебирать, то реализацию можно сделать практически такую же как у тебя:
А для того, чтобы эта шутка работало не только для частного случая список, но и для общего случая — достаточно абстракции Foldable.
ВВ>Кстати, если считать, что на входе у нас ['a]], т.е. структура данных, про которую мы знаем, как ее можно перебирать, то реализацию можно сделать практически такую же как у тебя:
Спасибо! С ExtLib и чуть обобщив это выглядит так:
ВВ>А для того, чтобы эта шутка работало не только для частного случая список, но и для общего случая — достаточно абстракции Foldable.
Здесь нам нужно, чтобы для нашей последовательности типа 'elt seq (последовательность элементов типа 'elt) была доступна функция
iter : ('elt -> 'elt seq option) -> 'elt seq -> 'elt seq option
Осталось понять, как ее реализовать для той штуки, что в описана первом посте вторым вариантом — где 'elt seq это 'elt -> int -> bool
(Func<T,Int32,Boolean>).
Я имел в виду именно эфективную реализацию.
ВВ>Т.е. итераторы просто сахар? Зачем они нужны?
Итератор является шаблоном проектирования. Как идея он не зависит от конкретной реализации в каком-либо языке, или в библиотеке алгоритмов. Нужен он:
1. для последовательного прохождения по любой структуре данных (линейный список, граф и так далее)
2. подмены передачи последовательности прохождения по структуре в алгоритме (чтобы не переписывать сам алгоритм множество раз (китайский код, копипаста))
В свою очередь данные пункты так же увеличивают гибкость программной системы. Её части можно писать, отлаживать и усовершенствовать независимо друг от друга. В конечном счёте код пишут люди, а они ошибаются. Потому данные подход так же помогает увеличить надёжность системы.
типичный итератор:
a. установка на начало последовательности (возвращает итератор)
b. проверка на конец последовательности (возвращает конец или нет)
c. переход к следующему элементу последовательности (ничего не возвращает)
d. получить текущий элемент структуры (возвращает текущий элемент структуры на котором остановилась последовательность)
Даже если изменить строение итератора с классического на иной, всё равно исходная идея отделения логики навигации по структуре данных останется. Можно слить получение текущего элемента с переходом на следующий элемент.
(d)получение текущего элемента + (c)переход на следующий элемент = получить следующий элемент
Получится тоже вполне себе итератор, только при использовании элемента более одного раза придётся куда-то записывать способ указания на сам элемент. В трансформации итератора можно пойти и дальше, получив то, что здесь было названо функцией замыкания. Его логика возможно потребует более сложного решения, чем take + 1, где take типа int. Иными словами как бы не меняли итератор, в итоге скорее всего получим менее удобный и более корявый итератор, чем тот, который уже стал классикой, но это будет тоже итератор. А иначе попросту лишимся возможностей пунктов 1 и 2.
P.S. Ещё существует потоковый итератор, обычно ему приписывают I/O, то есть ввод/вывод. Хотя идея схожая, с разницей, что данные поступают не элементами, а кусочками. Тоже своего рода понятие. Мышления на более высоких уровнях абстракции, цель которых не усложнить, а упростить жизнь разработчикам.