Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В качестве наглядного примера можно взять итераторы в C# или даже более продвинутую реализацию. Неважно.
Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
Попробую объяснить, что я имею в виду. Положим, некий код вида:
IEnumerable<Int32> Range(int start, int end)
{
var e = end + 1;
for (var i = start; i < e; i++)
yield return i;
}
foreach (var i in Range(0, 100)) {
//Do something
}
Можно переписать вот так:
void Range(int start, int end, Action<Int32> fun)
{
var e = end + 1;
for (var i = start; i < e; i++)
fun(i);
}
Можно представить так и работу с бесконечными последовательностями, если действие будет описывать как Func<T,Int32,Boolean>, где второй параметр — порядковый номер элемента, а первый сам элемент. Возвращается же флажок, по которому определяются следует ли нам продолжать. Создавать такую функцию можно через простейший комбинатор вида:
Func<T,Int32,Boolean> Create(Action<T> fun, int take)
{
var t = take + 1;
return (e, i) => {
if (i < t) {
fun(e);
return true;
}
else
return false;
};
}
Т.е. с точки зрения юзабилити это, конечно, менее удобно, но вот, собственно, и все.
В принципе у итераторов как частного случая корутин должны быть более широкие применения вроде как, но все, о чем я могу подумать, прекрасно выражается через ФВП. Причем вариант с ФВП отлично дружит с continuation-ами, с которыми те же C#-вые итераторы не дружат совсем.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В качестве наглядного примера можно взять итераторы в C# или даже более продвинутую реализацию. Неважно.
ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
ВВ>Попробую объяснить, что я имею в виду. Положим, некий код вида:
ВВ>
ВВ>IEnumerable<Int32> Range(int start, int end)
ВВ>{
ВВ> var e = end + 1;
ВВ> for (var i = start; i < e; i++)
ВВ> yield return i;
ВВ>}
ВВ>foreach (var i in Range(0, 100)) {
ВВ> //Do something
ВВ>}
ВВ>
ВВ>Можно переписать вот так:
ВВ>
ВВ>void Range(int start, int end, Action<Int32> fun)
ВВ>{
ВВ> var e = end + 1;
ВВ> for (var i = start; i < e; i++)
ВВ> fun(i);
ВВ>}
ВВ>
ВВ>Можно представить так и работу с бесконечными последовательностями, если действие будет описывать как Func<T,Int32,Boolean>, где второй параметр — порядковый номер элемента, а первый сам элемент. Возвращается же флажок, по которому определяются следует ли нам продолжать. Создавать такую функцию можно через простейший комбинатор вида:
ВВ>
ВВ>Func<T,Int32,Boolean> Create(Action<T> fun, int take)
ВВ>{
ВВ> var t = take + 1;
ВВ> return (e, i) => {
ВВ> if (i < t) {
ВВ> fun(e);
ВВ> return true;
ВВ> }
ВВ> else
ВВ> return false;
ВВ> };
ВВ>}
ВВ>
ВВ>Т.е. с точки зрения юзабилити это, конечно, менее удобно, но вот, собственно, и все.
ВВ>В принципе у итераторов как частного случая корутин должны быть более широкие применения вроде как, но все, о чем я могу подумать, прекрасно выражается через ФВП. Причем вариант с ФВП отлично дружит с continuation-ами, с которыми те же C#-вые итераторы не дружат совсем.
ВВ>Т.е. итераторы просто сахар? Зачем они нужны?
Ну как-бы исторический фактор. Сначала были сделаны IEnumerator\IEnumerable и сахар в виде foreach для них. Потом разработчики компиляторы подумали что писать руками итераторы слишком сложно и сделали сахар в виде yield return для них.
Кроме того continuations в императивном стиле очень тяжело писать (без сахара вроде async\await).
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В качестве наглядного примера можно взять итераторы в C# или даже более продвинутую реализацию. Неважно.
ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
ВВ>Т.е. итераторы просто сахар? Зачем они нужны?
Поздравляю с открытием CPS Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.
Здравствуйте, gandjustas, Вы писали:
G>Ну как-бы исторический фактор. Сначала были сделаны IEnumerator\IEnumerable и сахар в виде foreach для них. Потом разработчики компиляторы подумали что писать руками итераторы слишком сложно и сделали сахар в виде yield return для них. G>Кроме того continuations в императивном стиле очень тяжело писать (без сахара вроде async\await).
Этот вопрос меня интересует вообще применительно к другому языку, я "перевожу стрелки" на C#, чтобы было проще обсуждать. Т.е. тяжело, неудобно писать — это все понятно. Интересно, дают ли что-либо итераторы, кроме "сахарности"?
Здравствуйте, lomeo, Вы писали:
ВВ>>Т.е. итераторы просто сахар? Зачем они нужны? L>Мне кажется, ты перевёл всё в CPS. Представь как будет выглядеть цепочка функций без итераторов.
На C# — не очень. На каком-нибудь более другом языке — вполне нормально. Скажем, альтернативой линковского Where(...).Select(...) будет композиция функций. Насколько это будет выглядеть лучше — вопрос относительный. Можно вообще сделать специальный сахар для создания каких-нибудь узко-специализированных композиций, которые будут покрывать такие сценарии.
У меня возникает вообще впечатление, что итераторы сами по себе мешают писать функциональный код. С рекурсией у них как-то тухло. Сами по себе они выступают в виде этакой альтернативы более привычным вещам, вроде созданию ФВП, композиции селекторов. Или может, есть какие-то более правильные итераторы, о которых я не знаю?
Вообще вопрос можно задать так — почему итераторы не нужны в Хаскеле?
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
ВВ> for (var i = start; i < e; i++) ВВ> fun(i);
Тебя, видимо, слишком уводит в сторону тут специфика C#. "В общем и целом" переменная i в данном случае — итератор. У тебя в примере он — целое число, но представь себе что-то более сложное: например, перебор элементов дерева, или ответа на SQL запрос. В этом случае ты уже не сможешь написать такой for в простом виде — и вынужден будешь или писать с учётом внутренностей реализации, или таки делать итератор. А будет этот итератор сделан через yield, через объект с методом next() или как-то ещё — уже неважно.
Здравствуйте, k.o., Вы писали:
ВВ>>Т.е. итераторы просто сахар? Зачем они нужны? KO>Поздравляю с открытием CPS
Мне кажется, его уже открыли до меня
KO>Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.
Если вопрос *только* в удобстве, почему в C# итераторы присутствуют как отдельная фича, да и компилятор там немало кода генерит? Достаточно было бы просто ввести некий сахар "для удобства" и все. Тогда бы у нас была фича, реализованная по классической схеме:
Здравствуйте, netch80, Вы писали:
N>Тебя, видимо, слишком уводит в сторону тут специфика C#. "В общем и целом" переменная i в данном случае — итератор. У тебя в примере он — целое число, но представь себе что-то более сложное: например, перебор элементов дерева, или ответа на SQL запрос. В этом случае ты уже не сможешь написать такой for в простом виде — и вынужден будешь или писать с учётом внутренностей реализации, или таки делать итератор. А будет этот итератор сделан через yield, через объект с методом next() или как-то ещё — уже неважно.
Я вообще спрашиваю о генераторах, если такой термин понятнее/привычнее. В дотнете IEnumerable выполняет две задачи — абстракция для перебора элементов (тут ОК, и эта тема вообще отдельная) и интерфейс функции-генератора (т.е. функции, которая умеет возвращать свое значение несколько раз и сохраняет весь свой стек между вызовами).
Итератор (курсор) — идиома ООП.
И вопрос, наверное, следовало бы поставить как: "Почему нужны итераторы" — и вот тут есть довольно резонный ответ — потому что, как правило, ООП-языки не держат функцию за первоклассный объект. Приходится выдумывать в рамках ООП костылёк. Я совершенно не удивлюсь, если мне скажут, что Visitor не нужен, потому что есть map()
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Здравствуйте, k.o., Вы писали:
ВВ>>>Т.е. итераторы просто сахар? Зачем они нужны? KO>>Поздравляю с открытием CPS
ВВ>Мне кажется, его уже открыли до меня
KO>>Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.
ВВ>Если вопрос *только* в удобстве, почему в C# итераторы присутствуют как отдельная фича, да и компилятор там немало кода генерит? Достаточно было бы просто ввести некий сахар "для удобства" и все. Тогда бы у нас была фича, реализованная по классической схеме:
ВВ>- Стандартный паттерн ВВ>- Сахарок сверху, повышающий удобство
ВВ>Что, неужели итераторы в том виде, в котором они есть, появились просто по "историческим причинам"?
А что такое порядковый номер? Есть масса контейнеров, где он невозможен или бессмысленен. А так можно писать алгоритм, который слабо зависит от контейнера
, Вы писали:
ВВ>Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В качестве наглядного примера можно взять итераторы в C# или даже более продвинутую реализацию. Неважно.
ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
ВВ>Попробую объяснить, что я имею в виду. Положим, некий код вида:
ВВ>
ВВ>IEnumerable<Int32> Range(int start, int end)
ВВ>{
ВВ> var e = end + 1;
ВВ> for (var i = start; i < e; i++)
ВВ> yield return i;
ВВ>}
ВВ>foreach (var i in Range(0, 100)) {
ВВ> //Do something
ВВ>}
ВВ>
ВВ>Можно переписать вот так:
ВВ>
ВВ>void Range(int start, int end, Action<Int32> fun)
ВВ>{
ВВ> var e = end + 1;
ВВ> for (var i = start; i < e; i++)
ВВ> fun(i);
ВВ>}
ВВ>
ВВ>Можно представить так и работу с бесконечными последовательностями, если действие будет описывать как Func<T,Int32,Boolean>, где второй параметр — порядковый номер элемента, а первый сам элемент. Возвращается же флажок, по которому определяются следует ли нам продолжать. Создавать такую функцию можно через простейший комбинатор вида:
ВВ>
ВВ>Func<T,Int32,Boolean> Create(Action<T> fun, int take)
ВВ>{
ВВ> var t = take + 1;
ВВ> return (e, i) => {
ВВ> if (i < t) {
ВВ> fun(e);
ВВ> return true;
ВВ> }
ВВ> else
ВВ> return false;
ВВ> };
ВВ>}
ВВ>
ВВ>Т.е. с точки зрения юзабилити это, конечно, менее удобно, но вот, собственно, и все.
ВВ>В принципе у итераторов как частного случая корутин должны быть более широкие применения вроде как, но все, о чем я могу подумать, прекрасно выражается через ФВП. Причем вариант с ФВП отлично дружит с continuation-ами, с которыми те же C#-вые итераторы не дружат совсем.
ВВ>Т.е. итераторы просто сахар? Зачем они нужны?
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Здравствуйте, netch80, Вы писали:
N>>Тебя, видимо, слишком уводит в сторону тут специфика C#. "В общем и целом" переменная i в данном случае — итератор. У тебя в примере он — целое число, но представь себе что-то более сложное: например, перебор элементов дерева, или ответа на SQL запрос. В этом случае ты уже не сможешь написать такой for в простом виде — и вынужден будешь или писать с учётом внутренностей реализации, или таки делать итератор. А будет этот итератор сделан через yield, через объект с методом next() или как-то ещё — уже неважно.
ВВ>Я вообще спрашиваю о генераторах, если такой термин понятнее/привычнее. В дотнете IEnumerable выполняет две задачи — абстракция для перебора элементов (тут ОК, и эта тема вообще отдельная) и интерфейс функции-генератора (т.е. функции, которая умеет возвращать свое значение несколько раз и сохраняет весь свой стек между вызовами).
Так тебя интересует полезность конструкции генератора именно в виде функции с yield? Если так — то вопрос сводится к тому, что другой дизайн требует реализации (хоть обычно и крошечной, но) FSM, а это для многих уже высший пилотаж.
Да, тогда можно форму с yield считать сахаром.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Здравствуйте, k.o., Вы писали:
ВВ>>>Т.е. итераторы просто сахар? Зачем они нужны? KO>>Поздравляю с открытием CPS
ВВ>Мне кажется, его уже открыли до меня
KO>>Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.
ВВ>Если вопрос *только* в удобстве, почему в C# итераторы присутствуют как отдельная фича, да и компилятор там немало кода генерит? Достаточно было бы просто ввести некий сахар "для удобства" и все. Тогда бы у нас была фича, реализованная по классической схеме:
ВВ>- Стандартный паттерн ВВ>- Сахарок сверху, повышающий удобство
ВВ>Что, неужели итераторы в том виде, в котором они есть, появились просто по "историческим причинам"?
Вобще, ИМХО, очевидно, что итераторы, в том виде, как они есть сейчас, можно безболезненно прикрутить к большинству .NET языков (если бы их там не было). При этом использование CPS потребовало бы совершенно другой реализации и для C# и для VB.NET и для виртуальной машины, причём есть сомнения в том, что эта реализация могла бы быть не менее эффективна.
Здравствуйте, Lloyd, Вы писали:
ВВ>>Вообще вопрос можно задать так — почему итераторы не нужны в Хаскеле? L>Потому что он весь из себя ленивый — ты свободно можешь создать бесконечный список и вернуть его из фнкции.
То, что я могу сделать *на самом деле* — это через итератор выразить некую non-strict последовательность *вычислений*. Это же я могу сделать и без итераторов через CPS. А для того, чтобы создать а) *список* и б) ленивый список с мемоизацией — мне и с итераторами придется повозиться. Т.е. итератор мне никак не заменит, скажем, такую функцию, которая пишется на языке с поддержкой ленивости, но без всяких итераторов:
let where f x::xs = if f x thenx :: (lazy where f xs)else where f xs
| f [] = [];
Здравствуйте, PoM-PoM 40mm, Вы писали:
PP4>Здравствуйте, Воронков Василий PP4>А что такое порядковый номер? Есть масса контейнеров, где он невозможен или бессмысленен. А так можно писать алгоритм, который слабо зависит от контейнера
Например, как? Все способы, предлагаемые линком, чтобы работать с циклическим итератором вида
for (;;) yield 1;
это именно "отщепить" кусочек. Причем кусочек "в цифрах". Будь то First или Take. Вы можете еще раскрутить его через foreach, а в нужный момент дернуть break — но для всего, что можно перебрать через foreach порядковый номер элемента вполне имеет смысл хотя бы рамках этого перебора.
Т.е. линк со своими итераторами и сам никаких особых техник работы с бесконечными последовательностями не предлагает.
Здравствуйте, netch80, Вы писали:
N>Так тебя интересует полезность конструкции генератора именно в виде функции с yield? Если так — то вопрос сводится к тому, что другой дизайн требует реализации (хоть обычно и крошечной, но) FSM, а это для многих уже высший пилотаж. N>Да, тогда можно форму с yield считать сахаром.
FSM не нужен, если есть первоклассные функции. А yield — это может и сахар, но никак не сахар для ФВП. Т.е. то, что дает yield, реализуется через ФВП, но сам yield сделан иначе. Мне интересно — почему.
ОК, спасибо, посмотрю.
L>Что касается вопроса — ответили уже про ленивость.
А что с ленивостью? То, что дает итератор в этом плане, прекрасно реализуется опять же без итератора через ФВП.
Кстати, если захочется, скажем, реализовать на C# свой связный список и сделать его ленивым с мемоизацией через итератор, то окажется, что стандартный итератор в этой задаче не поможет вообще никак и придется все делать ручками.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>А что с ленивостью? То, что дает итератор в этом плане, прекрасно реализуется опять же без итератора через ФВП.
Вопрос в другую сторону был поставлен. Почему в Haskell не нужны итераторы. Ответ — потому что ленивость уже решает эти задачи. Почему нужны в C# — может потому что так удобнее?
Здравствуйте, lomeo, Вы писали:
L>Вопрос в другую сторону был поставлен. Почему в Haskell не нужны итераторы. Ответ — потому что ленивость уже решает эти задачи. Почему нужны в C# — может потому что так удобнее?
У меня вообще схожие впечатления. Ленивость, необязательно неявная, если требуется non strict поведение — как при создании контейнеров, так и при вычислениях. И стандартные паттерны ФП для всего остального.
Т.е. при таком раскладе сама по себе функция-генератор оказывается как бы невостребованной.
Ну и для строгих функциональных языков родственное итератору понятие — перечисление тоже весьма полезно как средство
обобщенно представлять разные виды последовательностей (конечные и бесконечные, строгие и ленивые) работать с ними
однородно используя тот же набор ФВП, что и для списков и плюс изолировать в них грязь, как пример 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.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Да, вот только "перечислитель" в стиле IEnumerator, подобный которому предлагается и в BatEnum, как я понимаю, это достаточно императивная по своей природе штука.
Да галимый императив в реализации.
Но интерфейс практически чисто функциональный.
ВВ>Он предлагает обобщенный способ для работы с коллекциями, но *какой* способ?
Очень простой работать через базовые ФВП.
ВВ>Скажем, вместо красивого рекурсивного:
ВВ>
ВВ>let fold f z x::xs = fold f (f z x) xs
ВВ> | f z [] = z
ВВ>
ВВ>Я что буду писать? Это если мне захочется сделать свою функцию, которая что-то делает с энумератором.
В случае fold ничего ни будешь, он уже есть, также как и все базовые ФВП.
В этом-то и суть что есть база в виде ФВП и все остальное выражается через нее. Плюс list comprehensions в
батарейках по умолчанию дает Enum.
ВВ>Альтернативой тут может быть подход к абстракции для контейнера с другого конца — вот тот же Foldable.
Здравствуйте, FR, Вы писали:
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# или ленивыми списками это делается очень просто. А как это будет выглядеть с генераторами/ФВП?
On 24.11.2010 12:47, Воронков Василий wrote:
> Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик. В > качестве наглядного примера можно взять итераторы в C# или даже более > продвинутую реализацию. Неважно.
Вообще итераторы не нужны. Но придуманы они были для того, чтобы отделить
алгоритмы обработки коллекций от способов доступа к этим коллекциям,
чтобы алгоритмы были бы универсальными.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Вот честно, не флейма ради, но недавно подобный вопрос поставил меня в тупик.
Вот это как-то странно.
ВВ>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного.
Ну, во-первых, можно даже сделать более сильное утверждение: итераторы изоморфны событиям.
В твоём примере IEnumerable<int> лёгким движением руки превращается в Action<int>. Это — частный случай того, о чём много и убедительно пишет Барт.
Понятно, что сразу получаем обратный вопрос: а зачем, собственно, нужны события и возможность передавать свою функцию куда-то туда, откуда можно брать и вычитывать значения?
Ответ, конечно же, очевиден: удобным бывает разное представление одного и того же алгоритма. Кроме этого, надо понимать, что дотнет принципиально многоязыковая среда. Потреблять итератор можно и из языка, в котором нет замыканий; потреблять твой вариант — крайне тяжело.
Так что, таки да: "с точки зрения юзабилити это, конечно, менее удобно, но вот, собственно, и все."
Здравствуйте, Sinclair, Вы писали:
S>Ответ, конечно же, очевиден: удобным бывает разное представление одного и того же алгоритма.
Зачем?
S>Кроме этого, надо понимать, что дотнет принципиально многоязыковая среда. Потреблять итератор можно и из языка, в котором нет замыканий; потреблять твой вариант — крайне тяжело.
Язык, в котором нет функций-генераторов, сможет только вызвать такую функцию, и все. Причем для удобной работы с этими функциями все равно должна быть некоторая поддержка со стороны языка, иначе "крайне тяжело" будет работать и с обычными итераторами. Яркий пример — МС++, где даже for each для IEnumerable не было.
Первоклассные же функции имитируются в C# через делегаты, ибо только делегат можно передать в другую функцию. Делегат же — это уже не фича C#. Таким образом, ФВП, написанные на C#, вполне юзабельны и из других .NET-языков — насколько "тяжело" будет с ними работать зависит опять же от того, поддерживает ли язык специальный сахар для делегатов, умеет ли его компилятор создавать замыкания и пр.
Я не вижу здесь какой-то кардинальной разницы.
S>Так что, таки да: "с точки зрения юзабилити это, конечно, менее удобно, но вот, собственно, и все." S>Ну и, конечно же, как всегда, рекомендую почитать Липперта на тему применибельности CPS в C#.
Меня C# вообще мало интересует. Применимо там CPS или неприменимо, насколько оно совместимо с C# программистами и проч. Мне как раз интересно, были ли другие причины, кроме "в .NET есть и другие языки" или "CPS непонятен людям" сделать все так, а не иначе. Липперт как раз на этот вопрос не отвечает.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Меня C# вообще мало интересует. Применимо там CPS или неприменимо, насколько оно совместимо с C# программистами и проч.
Странный вы человек, сначала спрашиваете "нафига итераторы?", а на "в шарпе ими удобно пользоваться" отвечаете, что шарп вас не интересует
ВВ>Мне как раз интересно, были ли другие причины, кроме "в .NET есть и другие языки" или "CPS непонятен людям" сделать все так, а не иначе. Липперт как раз на этот вопрос не отвечает.
Отвечает, и неоднократно. Шарп — мейнстрим; соображения практического удобства ценятся на голову выше, чем абстрактная чистота. Для повышения ЧСВ есть куча других языков и платформ, к чему эти регулярные крестовые походы на <подставить фичу>?
Здравствуйте, Sinix, Вы писали:
ВВ>>Мне как раз интересно, были ли другие причины, кроме "в .NET есть и другие языки" или "CPS непонятен людям" сделать все так, а не иначе. Липперт как раз на этот вопрос не отвечает. S>Отвечает, и неоднократно.
Что отвечает? "Шарп — мейнстрим" это не ответ. Не тот ответ, который мне интересен.
S>Шарп — мейнстрим; соображения практического удобства ценятся на голову выше, чем абстрактная чистота. Для повышения ЧСВ есть куча других языков и платформ, к чему эти регулярные крестовые походы на <подставить фичу>?
Не занимаюсь я никакими крестовыми походами на <подставить фичу>. Говоря, что шарп меня интересует, я имею в виду, что не хочу склонять обсуждение темы к тому, насколько оправдано существование генераторов наряду с замыканиями конкретно в C#. Хотя бы потому что ответы будут "в стиле Липперта". Это не значит, что ответы плохие. Меня просто интересует другое.
Будет проще, если я спрошу, оправдано ли в неком абстрактном языке существование наряду с замыканиями функций-генераторов, какие задачи они могут решать и почему эти задачи не могут быть решены через ФВП с таким же удобством. Неужели обсуждать сферических коней в вакууме проще?
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Что отвечает? "Шарп — мейнстрим" это не ответ. Не тот ответ, который мне интересен.
Так вам уже по-всякому ответили. Делегаты рулят для простого расширения поведения. Итераторы — для работы с последовательностями. То, что одно можно выразить через другое, не значит что остаться должен кто-то один.
Если убирать итераторы — что вы будете делать с foreach? Как вы реализуете linq? Представляете примерный объём работы по переделке рантайма/BCL/документации/инструментария? Нафига?
ВВ>Будет проще, если я спрошу, оправдано ли в неком абстрактном языке существование наряду с замыканиями функций-генераторов, какие задачи они могут решать и почему эти задачи не могут быть решены через ФВП с таким же удобством. Неужели обсуждать сферических коней в вакууме проще?
Да, потому что можно забить на то, насколько таким языком будет удобно пользоваться, какая у него будет популярность и какие шансы на реализацию на нём платформы, аналогичной фреймворку.
Поймите, никого не колышет язык сам по себе, побеждает связка "язык+библиотеки+инструментарий+документация+community". Вы можете сделать сколько угодно сверхфичастых сфероязыков, но пока на них не будет удобно как клепать формочку для студенческой лабораторной, так и писать софт уровня студии — ваш гипотетический язык так и останется игрушкой для гиков.
Здравствуйте, Sinix, Вы писали:
ВВ>>Что отвечает? "Шарп — мейнстрим" это не ответ. Не тот ответ, который мне интересен. 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.
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
— паттерны по его поддержке
— ограничения на то, что можно/нельзя использовать в замыкании
С учётом этих данностей и было принято решение. Как я уже сказал, глобально — никаких причин не делать поддержку "обратных вызовов" нету.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, minorlogic, Вы писали:
ВВ>>Фактически все задачи, которые выполняются через итераторы, не менее успешно выполняются и через функцию-замыкание. Насколько я понимаю, в том же Руби итераторы по сути и представляют собой некий сахар для подобного. M>Гм... точно ? т.е. быструю сортировку успешно выполнить через замыкания ?
Здравствуйте, Sinclair, Вы писали:
ВВ>>Зачем? S>Очевидно, для компактности и воспринимаемости записанного кода.
Это причина, по которой в язык добавляют сахар. Но это не причина для добавления новых фич.
Зайдем с другой стороны. Предположим, что у нас есть воображаемый язык Blab. В языке Blab есть первоклассные функции и замыкания. Язык Blab существует сам по себе, без всяких дотнетов — более того, у него вообще свой собственный бэкенд, спроектированный специально для Blab-а.
Также предположим, что Blab — это функциональный язык.
Blab-еры как-то прознали о том, что существует такая хитрая фича — функции, которые могут возвращать значение несколько раз и к тому же умеют сохранять свое состояние между вызовами. На первый взгляд Блаберам кажется, что фича эта странная и стремная: какие-то побочные эффекты непонятные, вызов и использование таких функций будет сильно отличаться от обычных, с рекурсией внутри них тоже косяки, потом опять же на первый взгляд им кажется, что ничего эта фича в сущности не дает-то. Query Comprehension? Да это же простые комбинаторы. Отложенные вычисления? Так у Блабером для этого санки есть. А на санках и ленивые списки строятся. Через такие функции можно получить универсальный интерфейс для работы с контейнерами? Ну во-первых есть Foldable, а во-вторых — получить-то можно, а как работать с этими контейнерами придется, в императивном стиле? А в Блабе может и вообще циклов не быть.
Однако вот друзья-товарищи из Питона и Руби говорят, что фича мировая. Какие причины эти самые товарищи могут привести, чтобы убедить блаберов, что генераторы их языку, несмотря на вышесказанное, все же не помешают?
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Здравствуйте, minorlogic, Вы писали:
M>>Гм... точно ? т.е. быструю сортировку успешно выполнить через замыкания ?
ВВ>А что мешает-то? И причем тут вообще итераторы?
Возможно я заблуждаюсь , но не видел красивой и эфективной сортировки без итераторов. Дайте линк.
Здравствуйте, minorlogic, Вы писали:
ВВ>>А что мешает-то? И причем тут вообще итераторы? M>Возможно я заблуждаюсь , но не видел красивой и эфективной сортировки без итераторов. Дайте линк.
Вы мне сначала покажите то, что называете сортировкой на итераторах.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Однако вот друзья-товарищи из Питона и Руби говорят, что фича мировая. Какие причины эти самые товарищи могут привести, чтобы убедить блаберов, что генераторы их языку, несмотря на вышесказанное, все же не помешают?
Никакие. Потому что фичи добавляются не сами по себе, а в комплексе, чтобы сделать язык удобным для решения определённого класса задач. Выбор набора фич определяется исключительно религиозными предпочтениями + (если повезёт) целевой аудиторией языка.
Как правило, дизайн языка с самого начала включает в себя законченный набор, покрывающий основные фичи языка с минимальным разнообразием. Этот же набор используют базовые библиотеки + сторонние фреймворки.
Аналогичный подход применяется (в идеале) и при дальнейшем развитии языка, иначе он моментально превратится в помойку. И, увы, добавить что-то новое, чтобы оно не выглядело чужеродным обвесом, но и не ломало совместимость с каждым релизом всё сложнее и сложнее.
Так что пока блаберам не потребуется добавить ряд фич, которые удобно решаются на итераторах (и оочень неудобно решать ч/з ФВП), итераторов в блабе не будет. По той же причине в шарпе нет ФВП в чистом виде.
Здравствуйте, Воронков Василий, Вы писали:
M>>Гм... точно ? т.е. быструю сортировку успешно выполнить через замыкания ?
ВВ>А что мешает-то? И причем тут вообще итераторы?
Многие помнят, как просто выражается квиксорт ("ненастоящий") на Хаскеле со списками: фильтруем, вызываем рекурсивно, соединяем.
А как это будет выглядеть с ФВП (без итераторов и ленивых списков) Когда есть функция, куда можно передать свою, и она получит все значения последовательности.
И еще попрошу все же показать, как будет выглядеть мой пример с последовательным выводом строк из сотни файлов.
Здравствуйте, D. Mon, Вы писали:
DM>Многие помнят, как просто выражается квиксорт ("ненастоящий") на Хаскеле со списками: фильтруем, вызываем рекурсивно, соединяем. DM>А как это будет выглядеть с ФВП (без итераторов и ленивых списков) Когда есть функция, куда можно передать свою, и она получит все значения последовательности.
Во-первых, откуда вы взяли ограничение "без ленивых списков"? Они-то как под нож попали? А на ленивых списках тот же Линк имитируется один в один.
Во-вторых, сортировка в Линке построена в сущности на континуэшине. Там компаратор работает примерно так:
comp f next x y | r == 0 = next x y
| otherwise = r
where r = f x y
И сдается мне, что итераторы тут играют сильно побочную роль.
DM>И еще попрошу все же показать, как будет выглядеть мой пример с последовательным выводом строк из сотни файлов.
Было бы неплохо, если вы бы свой пример тоже показали. Я не очень понимаю, что имеется в виду под "последовательным выводом строк из сотни файлов".
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Здравствуйте, minorlogic, Вы писали:
ВВ>>>А что мешает-то? И причем тут вообще итераторы? M>>Возможно я заблуждаюсь , но не видел красивой и эфективной сортировки без итераторов. Дайте линк.
ВВ>Вы мне сначала покажите то, что называете сортировкой на итераторах.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Здравствуйте, 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 (* запускаем *)
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Blab-еры как-то прознали о том, что существует такая хитрая фича — функции, которые могут возвращать значение несколько раз и к тому же умеют сохранять свое состояние между вызовами.
А можно мне показать описание вот этой хитрой фичи? Потому что я знаком только с дотнетной реализацией итераторов, так вот она ведёт себя вовсе не так, как тут описано. Питоновая реализация, судя по описанию, тоже совсем не то.
ВВ> На первый взгляд Блаберам кажется, что фича эта странная и стремная: какие-то побочные эффекты непонятные, вызов и использование таких функций будет сильно отличаться от обычных, с рекурсией внутри них тоже косяки, потом опять же на первый взгляд им кажется, что ничего эта фича в сущности не дает-то.
Ну, если делать такую странную и стрёмную фичу, то конечно же непонятно, в чём её смысл.
Скорее всего, в Блабе эту фичу сделать и вовсе не получится. Он же функциональный, поэтому в нём не будет изменяемого итератора. Зато концепция ленивого списка может быть встроена в него с самого начала.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, D. Mon, Вы писали:
DM>С ленивыми списками разница между итераторами и генераторами размывается. Я специально выше говорил про энергичные языки.
Стоп. Генератор — это вполне конкретная фишка. Это функция, которая может возвращать свое значение несколько раз и может сохранять свое состояние между вызовами. Итератор — ну это просто паттерн.
ВВ>>Во-вторых, сортировка в Линке построена в сущности на континуэшине. Там компаратор работает примерно так:
DM>Кажется, это сравнение двух последовательностей. К сортировке оно не относится.
Это не сравнение двух последовательностей, это генерация ключей, по которым производится сортировка. Именно так и работает "цепочечная" сортировка в Linq вида OrderBy(...).ThenBy(...).ThenBy(...). Т.е. построена она на CPS по сути.
ВВ>>Было бы неплохо, если вы бы свой пример тоже показали. Я не очень понимаю, что имеется в виду под "последовательным выводом строк из сотни файлов". DM>Абстрагируясь от работы с файлами, суть такова. Пусть у нас есть несколько итераторов, выдающих последовательности строк "fileN lineM". Нужно вывести сначала все первые строчки, затем вторые и т.д.
DM>Простите за мой французский, но я напишу на Окамле DM>Теперь прошу аналог с ФВП и, если угодно, CPS.
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, Вы писали:
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.
Здравствуйте, Sinclair, Вы писали:
ВВ>>Blab-еры как-то прознали о том, что существует такая хитрая фича — функции, которые могут возвращать значение несколько раз и к тому же умеют сохранять свое состояние между вызовами. S>А можно мне показать описание вот этой хитрой фичи? Потому что я знаком только с дотнетной реализацией итераторов, так вот она ведёт себя вовсе не так, как тут описано. Питоновая реализация, судя по описанию, тоже совсем не то.
Со стороны пользователя практически так она себя и ведет. Только интерфейс вызова выглядит как (Current/MoveNext) и представлен в виде объекта, который, собственно, и инкапсулирует в себе состояние функции, ее как бы стек. В Питоне по-моему несколько иначе. И завершение исполнения там сигнализируется выбросом исключения.
S>Ну, если делать такую странную и стрёмную фичу, то конечно же непонятно, в чём её смысл. S>Скорее всего, в Блабе эту фичу сделать и вовсе не получится. Он же функциональный, поэтому в нём не будет изменяемого итератора.
Зависит. В Ocaml-е же есть BatEnum. Т.е. что-то же заставило OCaml-овцев, в отличие от Блаберов, таки прийти к выводу, что итераторы это есть хорошо, несмотря на то, что в языке вроде как нет и недостатка в фичах из Блаба.
S>Зато концепция ленивого списка может быть встроена в него с самого начала.
Ленивый список — это просто x :: thunk, т.е. это и концепцией-то назвать сложно, можно сказать, особенность самой структуры данных.
Здравствуйте, Воронков Василий, Вы писали:
DM>>Абстрагируясь от работы с файлами, суть такова. Пусть у нас есть несколько итераторов, выдающих последовательности строк "fileN lineM". Нужно вывести сначала все первые строчки, затем вторые и т.д.
ВВ>Ну например, мне тут по соседству приводили пример реализации паттерна Iteratee на Хаскеле. ВВ>http://okmij.org/ftp/Haskell/Iteratee/DEFUN08-talk-notes.pdf ВВ>Вкратце:
Ну вот и напишите, как пройтись параллельно по нескольким итераторам. (не в смысле многоядерности, а в смысле порядка выводимых элементов)
ВВ>Вообще при работе с файлами у нас и так есть некий итератор, поток, stream.
Да, есть, в окамле тоже. Это несущественно. Покажите, как с CPS по нескольким файлам пройтись.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Со стороны пользователя практически так она себя и ведет. Только интерфейс вызова выглядит как (Current/MoveNext) и представлен в виде объекта, который, собственно, и инкапсулирует в себе состояние функции, ее как бы стек. В Питоне по-моему несколько иначе. И завершение исполнения там сигнализируется выбросом исключения.
Нет. Со стороны пользователя эта фича ведёт себя совершенно иначе. Есть foreach, и он магическим образом пользуется Current/MoveNext/Dispose. Пользователю всё равно, что там унутре за неонка. К примеру, C++ итераторы устроены точно так же, несмотря на отстутствие замыканий и чего бы то ни было другого.
Резюме: функция со стороны пользователя никоим образом не умеет "возвращать значение несколько раз" или "сохранять своё состояние". Функция возвращает значение ровно однажды, это значение типа IEnumerator<T>. Насколько я успел понять диагональным методом, в питоне всё строго так же.
Реализация внутренностей c yield return строго ортогональна потреблению с foreach.
ВВ>Зависит. В Ocaml-е же есть BatEnum. Т.е. что-то же заставило OCaml-овцев, в отличие от Блаберов, таки прийти к выводу, что итераторы это есть хорошо, несмотря на то, что в языке вроде как нет и недостатка в фичах из Блаба.
Ну, тогда, наверное, надо спросить окамловцев. Я с BatEnum совершенно незнаком — почему и прошу показать мне описание. Задавать вопрос про BatEnum в терминах шарпа я считаю несколько экстравагантным.
S>>Зато концепция ленивого списка может быть встроена в него с самого начала.
ВВ>Ленивый список — это просто x :: thunk, т.е. это и концепцией-то назвать сложно, можно сказать, особенность самой структуры данных.
Ну так концепция ленивого списка == IEnumerable. Если она уже есть, то ещё один IEnumerable не нужен.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Кстати, если считать, что на входе у нас ['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>).
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Стоп. Генератор — это вполне конкретная фишка. Это функция, которая может возвращать свое значение несколько раз и может сохранять свое состояние между вызовами. Итератор — ну это просто паттерн.
Прошу прощения, я могу быть неточным в терминах. Под итератором я имел в виду IEnumerable из C# и Enum из Окамла — пассивный объект, выдающий элементы по запросу. Под генератором я имел в виду описанный в первом посте Func<T,Int32,Boolean> или его аналог в Руби (там, где for по последовательности превращается в вызов метода с передачей тела цикла в виде ФВП). Именно между ними я вижу принципиальную разницу, и мне интересно, как первое выразить через второе.
Здравствуйте, D. Mon, Вы писали:
DM>Осталось понять, как ее реализовать для той штуки, что в описана первом посте вторым вариантом — где 'elt seq это 'elt -> int -> bool DM>(Func<T,Int32,Boolean>).
Давай попробуем вот так: (Фукнцию filter почти не менял, изменение выделил болдом. Сделал новый итератор)
let rec iter arr i act =
act arr.(i);
if i < Array.length arr - 1 then
let next = iter arr (i + 1)
in `Some next
else
`None;;
let (~~) arr = iter arr 0;; (* just a construction helper *)let lst = [ ~~[| 1;2;3 |]; ~~[| 4;5;6 |]; ~~[| 7;8;9 |]; ];;
let rec filter f lst =
let rec filter' = function
| x::xs -> (matchx fwith
| `Some v -> v :: filter' xs
| `None -> filter' xs)
| [] -> []
in
match filter' lst with
| [] -> ()
| nl -> filter f nl;;
filter (Printf.printf "%d\n") lst;;
filter сам работает с функцией вида ('a->unit)->[>Some|None] — это по сути и есть интерфейс нашего итератора. Вот второй примерчик, где используются сразу два разных итератора — тот же, что и выше, и модифицированный для списков из предыдущего сообщения с тем же самым фильтром:
let rec iter arr i act =
act arr.(i);
if i < Array.length arr - 1 then
let next = iter arr (i + 1)
in `Some next
else
`None;;
let rec iter2 lst act =
match lst with
| x::xs -> act x; `Some (iter2 xs)
| [] -> `None;;
let (~~) arr = iter arr 0;; (* just a construction helper *)let lst = [ ~~[| 1;2;3 |]; ~~[| 4;5;6 |]; ~~[| 7;8;9 |]; ];;
let lst2 = [ iter2 [ 1;2;3 ]; iter2 [ 4;5;6 ]; iter2 [ 7;8;9 ]; ];;
let rec filter f lst =
let rec filter' = function
| x::xs -> (match x f with
| `Some v -> v :: filter' xs
| `None -> filter' xs)
| [] -> []
in
match filter' lst with
| [] -> ()
| nl -> filter f nl;;
let _ = filter (Printf.printf "%d\n") lst;;
let _ = filter (Printf.printf "%d\n") lst2;;
Здравствуйте, Воронков Василий, Вы писали:
ВВ>filter сам работает с функцией вида ('a->unit)->[>Some|None] — это по сути и есть интерфейс нашего итератора.
Спасибо! Занятно, что с полиморфными вариантами компилятор успешно выводит рекурсивный тип, а с обычными вариантами ругается на его рекурсивность.
А как будет выглядеть функция map для таких итераторов?
Чтобы map string_of_int ( ~~[| 1;2;3 |] ) давала последовательность строк в виде такого же итератора?
Здравствуйте, D. Mon, Вы писали:
ВВ>>filter сам работает с функцией вида ('a->unit)->[>Some|None] — это по сути и есть интерфейс нашего итератора. DM>Спасибо! Занятно, что с полиморфными вариантами компилятор успешно выводит рекурсивный тип, а с обычными вариантами ругается на его рекурсивность.
Да, но это ты, видимо, должен пояснить, что там ему не нравится Я все же OCaml слабо знаю.
DM>А как будет выглядеть функция map для таких итераторов? DM>Чтобы map string_of_int ( ~~[| 1;2;3 |] ) давала последовательность строк в виде такого же итератора?
Что-то такое, видимо:
(Привожу полный код)
let rec iter arr i act =
act arr.(i);
if i < Array.length arr - 1 then
let next = iter arr (i + 1)
in `Some next
else
`None;;
let rec iterOther mact other act =
let v = other (fun x -> act (mact x)) in
match v with
| `Some v -> `Some (iterOther mact v)
| `None -> `None;;
let (~~) arr = iter arr 0;; (* just a construction helper *)
(* That's the function you've asked :) *)let rec map' f it = iterOther f it;;let res = map' string_of_int ~~[| 1;2;3 |];;
(* we've already constructed a new iterator, just checking what's inside *)let rec each' f it =
match it f with
| `Some v -> each' f v
| `None -> ()
in each' (Printf.printf "%s\n") res;;
И чем дальше в лес, тем все больше и больше это начинает напоминать Linq
Здравствуйте, Воронков Василий, Вы писали:
DM>>Спасибо! Занятно, что с полиморфными вариантами компилятор успешно выводит рекурсивный тип, а с обычными вариантами ругается на его рекурсивность.
ВВ>Да, но это ты, видимо, должен пояснить, что там ему не нравится Я все же OCaml слабо знаю.
Заглянул в доку. По умолчанию рекурсивные типы разрешены только с полиморфными вариантами или с объектами. Для других типов рекурсию можно включить ключиком компилятора -rectypes, но это иногда может приводить к сюрпризам.
Recursive types for which there exists a recursive path that does not contain an object or polymorphic variant type constructor are rejected, except when the -rectypes mode is selected.
ВВ>И чем дальше в лес, тем все больше и больше это начинает напоминать Linq
Так и должно быть. Просто здесь состояние передается от вызова к вызову, а не прячется внутри объекта. Пойду еще подумаю, остались ли у меня вопросы.
Здравствуйте, lomeo, Вы писали:
L>Вопрос в другую сторону был поставлен. Почему в Haskell не нужны итераторы. Ответ — потому что ленивость уже решает эти задачи.
Не совсем приемлемый ответ. Все же списки, даже если они будут ленивыми, остаются конкретными структурами данных, а не абстракциями. Итератор шарпа же — это абстракция. Мы можем реализовать итератор поверх любой коллекции или даже без оной (пример — все тот же Range()).
L>Почему нужны в C# — может потому что так удобнее?
+1
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Этот вопрос меня интересует вообще применительно к другому языку, я "перевожу стрелки" на C#, чтобы было проще обсуждать. Т.е. тяжело, неудобно писать — это все понятно. Интересно, дают ли что-либо итераторы, кроме "сахарности"?
Если тебя интересует выражаются ли итераторы шарпа через замыкания (точнее даже будет сказать, через монады) и т.п., то таки да, выражаются. Как и наоборот. Только вот это не говорит, что итераторы шарпа чем-то плохи. Реализация на без ДКА вполне эффективна и проста.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Воронков Василий, Вы писали:
KO>>Да, итераторы, исключения и любой вид управления control-flow выражается через CPS, вопрос, как всегда в удобстве.
ВВ>Если вопрос *только* в удобстве, почему в C# итераторы присутствуют как отдельная фича,
Да потому что они еще и через ДКА выражаются, и еще несколькими видами. Реализовывать ДКА или CPS вручную — это не очень благодарное занятие. А в виде фичи языка все выглядит просто, императивно (последовательно) и результат получается эффективный.
ВВ>да и компилятор там немало кода генерит?
Компилятор генерирует столько сколько нужно. Реализация получается весьма эффективной. Не хуже чем при использовании CSP.
Кстати, не все такой подход избрали. В F# для аналога генераторов используются Computation Expressions, то есть по сути CPS. В C# 5 появится sync/await. Реализация будет опять же на основе ДКА (точнее даже на основе итераторов). В F# и Nemerle есть аналогичная фича реализованная на базе Computation Expressions. Однако реализация на базе ДКА будет ничем не хуже. Так что возможно в немреле и ее реализуем.
ВВ>Достаточно было бы просто ввести некий сахар "для удобства" и все. Тогда бы у нас была фича, реализованная по классической схеме:
ВВ>- Стандартный паттерн ВВ>- Сахарок сверху, повышающий удобство
Ну, вот Computation Expressions и есть такой подход. Наверно и с итераторами и sync/await можно было так поступить. Но Хейльсберг все в язык тащит.
Но если тогда уж задуматься, то лучшим выходом было бы просто ввести макросы, а уже все подобные детали реализовать на их основе.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Wolverrum, Вы писали:
W>Итератор (курсор) — идиома ООП. W>И вопрос, наверное, следовало бы поставить как: "Почему нужны итераторы" — и вот тут есть довольно резонный ответ — потому что, как правило, ООП-языки не держат функцию за первоклассный объект. Приходится выдумывать в рамках ООП костылёк. Я совершенно не удивлюсь, если мне скажут, что Visitor не нужен, потому что есть map()
А я не удивлюсь если тебе замеят, что шаблон Посетитель и функция map решают разные задачи.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, PoM-PoM 40mm, Вы писали:
PP4>А что такое порядковый номер? Есть масса контейнеров, где он невозможен или бессмысленен. А так можно писать алгоритм, который слабо зависит от контейнера
Это у него такой пунктик. Он весьма странно понимает абстракции. Считает что массив и список это абстракции, а следовательно можно смело пользоваться индексом для выражения порядка элементов списка.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
ВВ>>Т.е. при таком раскладе сама по себе функция-генератор оказывается как бы невостребованной. VD>В указанной ссылке как раз презентация описывающая, что аналог итератора куда лучше чем "хэндл-бэзед ИО".
В презентации описывается реализация паттерна итератор на базе ФВП, о чем я и говорю.
Здравствуйте, VladD2, Вы писали:
L>>Вопрос в другую сторону был поставлен. Почему в Haskell не нужны итераторы. Ответ — потому что ленивость уже решает эти задачи. VD>Не совсем приемлемый ответ. Все же списки, даже если они будут ленивыми, остаются конкретными структурами данных, а не абстракциями. Итератор шарпа же — это абстракция. Мы можем реализовать итератор поверх любой коллекции или даже без оной (пример — все тот же Range()).
Под ленивостью я понимал ленивость, а не ленивые списки. А под итераторами — функции с yield.
Здравствуйте, lomeo, Вы писали:
L>>>Вопрос в другую сторону был поставлен. Почему в Haskell не нужны итераторы. Ответ — потому что ленивость уже решает эти задачи. VD>>Не совсем приемлемый ответ. Все же списки, даже если они будут ленивыми, остаются конкретными структурами данных, а не абстракциями. Итератор шарпа же — это абстракция. Мы можем реализовать итератор поверх любой коллекции или даже без оной (пример — все тот же Range()).
L>Под ленивостью я понимал ленивость, а не ленивые списки.
Ну, и что меняет ленивость в этом плане? Что ты возвращаешь из своих фукнций списки или некие классы типов вроде фолбадб?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
L>>Под ленивостью я понимал ленивость, а не ленивые списки. VD>Ну, и что меняет ленивость в этом плане? Что ты возвращаешь из своих фукнций списки или некие классы типов вроде фолбадб?
Что значит что меняет? Ты сказал вещи, совершенно не относящиеся к моему ответу, т.е. понял мой ответ как-то по своему.
Ленивость в этом плане меняет всё. foreach может замениться, например, на простой вызов функции для обхода ленивой структуры. Специальная конструкция не нужна.
Кстати, а какой будет typeof у результата функции с yield-ами?
Здравствуйте, lomeo, Вы писали:
L>Ленивость в этом плане меняет всё. foreach может замениться, например, на простой вызов функции для обхода ленивой структуры. Специальная конструкция не нужна.
А мне foreach нравится. Как-то недвусмысленнен он. Отлаживат и читать код с foreach легче, чем код с ФВП.
Здравствуйте, hardcase, Вы писали:
L>>Ленивость в этом плане меняет всё. foreach может замениться, например, на простой вызов функции для обхода ленивой структуры. Специальная конструкция не нужна. H>А мне foreach нравится. Как-то недвусмысленнен он. Отлаживат и читать код с foreach легче, чем код с ФВП.
Во-первых, foreach можно очень близко повторить.
class ForEach f where
toEnumerator :: f a -> [a]
foreach :: (ForEach f, Monad m) => f a -> (a -> m ()) -> m ()
foreach = mapM_ . toEnumerator
foreach collection (\item ->
doSomethingWith item
)
Во-вторых, foreach поощряет стиль "берём коллекцию, а работаем с элементом", вместо более высокоуровневого стиля "берём коллекцию и с ней же и работаем" — сравни с LINQ-ом.
В-третьих, да, видимо для большинста код с foreach проще, чем с ФВП. Но выводы из этого можно сделать какие угодно
Здравствуйте, hardcase, Вы писали:
L>>В-третьих, да, видимо для большинста код с foreach проще, чем с ФВП. Но выводы из этого можно сделать какие угодно H>В простых сценариях map/filter да удобны, а вот fold-ы уже наводят тоску при рефакторинге.
fold-ы низкоуровневы, ага. Лучше на них описать более абстрактные функции и с ними уже работать.
А можно пример, чтобы понять о чём речь?
Здравствуйте, lomeo, Вы писали:
L>fold-ы низкоуровневы, ага. Лучше на них описать более абстрактные функции и с ними уже работать. L>А можно пример, чтобы понять о чём речь?
Эт мой немерловый опыт написания и поддержки кода, работающего со списками. Впрочим, foreach в Nemerle это больше чем просто конструкция для итерации.
Здравствуйте, VladD2, Вы писали:
VD>Ну, и что меняет ленивость в этом плане? Что ты возвращаешь из своих фукнций списки или некие классы типов вроде фолбадб?
Ленивость меняет то, что свертка сворачивает контейнер в "итератор" лениво.
toList :: Foldable t => t a -> [a]
toList = foldr (:) []
А в энергичном языке придется делать ленивую свертку, чтобы реализовать аналог Foldable — что сложнее.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, hardcase, Вы писали:
H>В простых сценариях map/filter да удобны, а вот fold-ы уже наводят тоску при рефакторинге.
Это все изменится с опытом. Дело привычки, и со временем fold-ы будут наводить радость при рефакторинге, потому как такие вещи позволяют рефакторить автоматически и полуавтоматически довольно сложные вещи. Почитайте "Pearls of functional algorithm design" Ричарда Берда.
Здравствуйте, lomeo, Вы писали:
L>А можно пример, чтобы понять о чём речь?
Вот например (красный FoldLeft в строке 83). Автор этого шедевра этой жести — ваш покорный слуга. После 20 минутной медитации над FoldLeft-ом + RevMap-ом переписал на два foreach-а. Код стал понятнее и потом пережил еще пару рефакнторингов — это конвертер switch конструкции C# в match Nemerle. Иммутабельность это хорошо, когда без фанатизма.
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, hardcase, Вы писали:
H>>В простых сценариях map/filter да удобны, а вот fold-ы уже наводят тоску при рефакторинге.
K>Это все изменится с опытом. Дело привычки, и со временем fold-ы будут наводить радость при рефакторинге, потому как такие вещи позволяют рефакторить автоматически и полуавтоматически довольно сложные вещи.
Покачто мой опыт говорит обратное. Рефакторинг FoldLeft со сложной логикой внутри через некоторое время после его написания способен заставить автора всерьез пересмотреть свое отношение к программированию.
Здравствуйте, lomeo, Вы писали:
L>Ленивость в этом плане меняет всё. foreach может замениться, например, на простой вызов функции для обхода ленивой структуры. Специальная конструкция не нужна.
Ты эти сказки уже много раз рассказывал, но объяснить что дает ленивость в случае если мы имеем дело не со списками (в широком смысле этого слова, например, с деревьями тоже) ты так и не удосужился.
L>Кстати, а какой будет typeof у результата функции с yield-ами?
IEnumerable[X]
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Klapaucius, Вы писали:
K>Ленивость меняет то, что свертка сворачивает контейнер в "итератор" лениво. K>
K>toList :: Foldable t => t a -> [a]
K>toList = foldr (:) []
K>
K>А в энергичном языке придется делать ленивую свертку, чтобы реализовать аналог Foldable — что сложнее.
Сложнее но вполне реализуемо.
Кстати, я не могу понять смысла в ленивом fold-е. Ведь если я использую fold, то в общем случае я хочу получить какое-то значение. И только если я получаю какой-то список, то имеет смысл в ленивости, а при получения, скажем, целого числа, что мне даст ленивость?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Сложнее но вполне реализуемо.
Да, реализуемо.
VD>Кстати, я не могу понять смысла в ленивом fold-е. Ведь если я использую fold, то в общем случае я хочу получить какое-то значение. И только если я получаю какой-то список, то имеет смысл в ленивости,
Ну да, именно для построения какой-то сложной структуры ленивость фолда и нужна.
VD>а при получения, скажем, целого числа, что мне даст ленивость?
Ничего не даст. Сворачивать что-то в целое число нужно энергичным фолдом.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476>>
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, VladD2, Вы писали:
L>>Ленивость в этом плане меняет всё. foreach может замениться, например, на простой вызов функции для обхода ленивой структуры. Специальная конструкция не нужна. VD>Ты эти сказки уже много раз рассказывал, но объяснить что дает ленивость в случае если мы имеем дело не со списками (в широком смысле этого слова, например, с деревьями тоже) ты так и не удосужился.
- Папа, это что?
— Море, море, море!
— Папа, а что это было?
Да и разговор зачем переводишь? Конкретно речь идёт о том, что в итераторах вообще и в конструкции foreach в частности нет необходимости в случае ленивости по умолчанию. Не согласен? Приведи пример, когда недостаточно ленивости.
L>>Кстати, а какой будет typeof у результата функции с yield-ами? VD>IEnumerable[X]
Конкретный тип какой будет? Вот в python, например, generator. В Scala — такой же, как и внешний тип. А в C#?
Здравствуйте, lomeo, Вы писали:
L>>>Кстати, а какой будет typeof у результата функции с yield-ами? VD>>IEnumerable[X] L>Конкретный тип какой будет? Вот в python, например, generator. В Scala — такой же, как и внешний тип. А в C#?
Там фишка в том, что компилятор генерирует для тебя реализацию интерфейса IEnumerable<>.
Здравствуйте, VladD2, Вы писали:
VD>Кстати, не все такой подход избрали. В F# для аналога генераторов используются Computation Expressions, то есть по сути CPS. В C# 5 появится sync/await. Реализация будет опять же на основе ДКА (точнее даже на основе итераторов). В F# и Nemerle есть аналогичная фича реализованная на базе Computation Expressions. Однако реализация на базе ДКА будет ничем не хуже. Так что возможно в немреле и ее реализуем.
В F# есть "yield!". Как он будет работать в варианте с ДКА?
L>Да и разговор зачем переводишь? Конкретно речь идёт о том, что в итераторах вообще и в конструкции foreach в частности нет необходимости в случае ленивости по умолчанию. Не согласен? Приведи пример, когда недостаточно ленивости.
приведи, пожалуйста, критерий недостаточности.
если брать определение недостаточности по тьюрингу, то даже ленивость не нужна, а достаточно из всего хаскелая 0, 1 и двунаправленного списка.
Здравствуйте, hardcase, Вы писали:
H>Вот например (красный FoldLeft в строке 83). Автор этого шедевра этой жести — ваш покорный слуга. После 20 минутной медитации над FoldLeft-ом + RevMap-ом переписал на два foreach-а. Код стал понятнее и потом пережил еще пару рефакнторингов — это конвертер switch конструкции C# в match Nemerle. Иммутабельность это хорошо, когда без фанатизма.
FoldLeft — это вообще не свёртка (катаморфизм), а просто итерация.
Что касается кода, то я вижу, что в fold намешано сразу несколько вещей — группировка по условию, отображение группы на свёртку. Стоит разбить, должно получиться сильно проще.
Здравствуйте, DarkGray, Вы писали:
L>>Да и разговор зачем переводишь? Конкретно речь идёт о том, что в итераторах вообще и в конструкции foreach в частности нет необходимости в случае ленивости по умолчанию. Не согласен? Приведи пример, когда недостаточно ленивости. DG>приведи, пожалуйста, критерий недостаточности. DG>если брать определение недостаточности по тьюрингу, то даже ленивость не нужна, а достаточно из всего хаскелая 0, 1 и двунаправленного списка.
О чём в этом треде вообще речь? Выразительность, наверное. Скажем, есть пример с итераторами, который выглядит выразительнее, чем схожее решение с использованием ленивости по умолчанию?
Здравствуйте, hardcase, Вы писали:
L>>Конкретный тип какой будет? Вот в python, например, generator. В Scala — такой же, как и внешний тип. А в C#? H>А в C# — System.Collections.Generic.IEnumerable<T>.
Я совсем не знаю C#, но мне казалось, что конкретный объект должен иметь конкретный (пусть и сгенеренный, возможно, анонимный) тип, который реализует интерфейсы. Т.е. как бы это выглядело в Java — был бы сгенерен некий класс, имплементирующий интерфейс IEnumerable<T> и пользоваться его экземплярами мы могли бы только через этот интерфейс (ну ещё Object, но это неважно).
Я к чему этот разговор затеял — если это так и есть, как я предполагаю, то этот IEnumerable<T> мало чем от ленивого списка отличается. И все функции, возвращающие IEnumerable<T> аналогичны функциям, возвращающим [a] в ленивом языке. Маловато абстракции здесь по возвращаемому типу.
L>Я совсем не знаю C#, но мне казалось, что конкретный объект должен иметь конкретный (пусть и сгенеренный, возможно, анонимный) тип, который реализует интерфейсы. Т.е. как бы это выглядело в Java — был бы сгенерен некий класс, имплементирующий интерфейс IEnumerable<T> и пользоваться его экземплярами мы могли бы только через этот интерфейс (ну ещё Object, но это неважно).
в C# всё не так:
есть только интерфейс IEnumerable<X>, и есть функция которая этот интерфейс возвращает, и больше ничего нет.
т.е. наверху сплошная функциональщина и никаких объектов
Здравствуйте, DarkGray, Вы писали:
DG>в C# всё не так: DG>есть только интерфейс IEnumerable<X>, и есть функция которая этот интерфейс возвращает, и больше ничего нет. DG>т.е. наверху сплошная функциональщина и никаких объектов
Хм. Плохо понимаю, но допустим. Тогда вопрос в силе — есть какая-то разница на уровне абстракций между функцией, возвращающей с помощью yield, IEnumerator<T> и функцией, возвращающей ленивый список?
И для самообразования. Я прочитал, что
When the compiler detects your iterator, it will automatically generate the Current, MoveNext and Dispose methods of the IEnumerator or IEnumerator<T> interface.
Таким образом, генерится заглушка, реализующая интерфейс. А вот тип этой заглушки при запросе GetType вернёт IEnumerator<T> — так сделано? Проверить, к сожалению, не могу, поэтому спрашиваю. Сомнения возникли из определения GetType:
Return Value The Type instance that represents the exact runtime type of the current instance.
Здравствуйте, lomeo, Вы писали:
L>Здравствуйте, DarkGray, Вы писали:
DG>>в C# всё не так: DG>>есть только интерфейс IEnumerable<X>, и есть функция которая этот интерфейс возвращает, и больше ничего нет. DG>>т.е. наверху сплошная функциональщина и никаких объектов
L>Хм. Плохо понимаю, но допустим. Тогда вопрос в силе — есть какая-то разница на уровне абстракций между функцией, возвращающей с помощью yield, IEnumerator<T> и функцией, возвращающей ленивый список?
L>И для самообразования. Я прочитал, что L>
L>When the compiler detects your iterator, it will automatically generate the Current, MoveNext and Dispose methods of the IEnumerator or IEnumerator<T> interface.
Тип заглушки будет таким, каким мы его объявили:
IEnumerable<int> Range(int start, int end)
{
for(var i = start; i <= end; ++i)
yield return i;
}
L>Хм. Плохо понимаю, но допустим. Тогда вопрос в силе — есть какая-то разница на уровне абстракций между функцией, возвращающей с помощью yield, IEnumerator<T> и функцией, возвращающей ленивый список?
в целом — нет. разница может быть только в мелких нюансах
L>И для самообразования. Я прочитал, что L>
L>When the compiler detects your iterator, it will automatically generate the Current, MoveNext and Dispose methods of the IEnumerator or IEnumerator<T> interface.
L>Таким образом, генерится заглушка, реализующая интерфейс. А вот тип этой заглушки при запросе GetType вернёт IEnumerator<T> — так сделано?
там будет какое-нибудь страшное генеренное имя, ничего не говорящее. например: "<object>d__3<TResult, TItem>"
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Т.е. итераторы просто сахар? Зачем они нужны?
Итератор является шаблоном проектирования. Как идея он не зависит от конкретной реализации в каком-либо языке, или в библиотеке алгоритмов. Нужен он:
1. для последовательного прохождения по любой структуре данных (линейный список, граф и так далее)
2. подмены передачи последовательности прохождения по структуре в алгоритме (чтобы не переписывать сам алгоритм множество раз (китайский код, копипаста))
В свою очередь данные пункты так же увеличивают гибкость программной системы. Её части можно писать, отлаживать и усовершенствовать независимо друг от друга. В конечном счёте код пишут люди, а они ошибаются. Потому данные подход так же помогает увеличить надёжность системы.
типичный итератор:
a. установка на начало последовательности (возвращает итератор)
b. проверка на конец последовательности (возвращает конец или нет)
c. переход к следующему элементу последовательности (ничего не возвращает)
d. получить текущий элемент структуры (возвращает текущий элемент структуры на котором остановилась последовательность)
Даже если изменить строение итератора с классического на иной, всё равно исходная идея отделения логики навигации по структуре данных останется. Можно слить получение текущего элемента с переходом на следующий элемент.
(d)получение текущего элемента + (c)переход на следующий элемент = получить следующий элемент
Получится тоже вполне себе итератор, только при использовании элемента более одного раза придётся куда-то записывать способ указания на сам элемент. В трансформации итератора можно пойти и дальше, получив то, что здесь было названо функцией замыкания. Его логика возможно потребует более сложного решения, чем take + 1, где take типа int. Иными словами как бы не меняли итератор, в итоге скорее всего получим менее удобный и более корявый итератор, чем тот, который уже стал классикой, но это будет тоже итератор. А иначе попросту лишимся возможностей пунктов 1 и 2.
P.S. Ещё существует потоковый итератор, обычно ему приписывают I/O, то есть ввод/вывод. Хотя идея схожая, с разницей, что данные поступают не элементами, а кусочками. Тоже своего рода понятие. Мышления на более высоких уровнях абстракции, цель которых не усложнить, а упростить жизнь разработчикам.