Здравствуйте, AlexRK, Вы писали:
EP>>Это какие замыкания в C# по счёту? Третьи? EP>>Это и так умеет лямбда. ARK>Я бы вместо добавления локальных функций урезал лямбды — запретил им быть замыканиями
То что лямбды являются замыканиями — для меня одна из главных фич, так как это позволяет сократить O(N) количество кода захвата до O(1).
ARK>и содержать больше одного выражения.
В Python примерно так и есть — там у лямбд только один statement (правда они в отличие от твоего предложения всё же замыкания), а для больших замыканий есть локальные функции.
ARK>С целью слегка ограничить полет фантазии особо одаренных.
От этого помогает code review, так как такая фантазия может улететь куда угодно.
ARK>Правда, уверен, в текущем обсуждении меня мало кто поддержит.
А мне нравятся мощные, быстрые, полиморфные (типа "generic") лямбды — как в C++
Здравствуйте, Sinix, Вы писали:
S>Казалось бы, идеальный пример для local functions.
Не уверен. Идеальный пример для local functions — рекурсивные функции. Практически всегда сигнатура и предусловия вызова функции внешним клиентом отличается от самовызова функции в её реализации. Поэтому стабы рекурсивных функций типа такой
public long Fib(int n)
{
throw new NotImplementedException();
}
можно сразу переписать (на псевдокоде) как
public long Fib(int n)
{
local long FibRecursive(int n)
{
throw new NotImplementedException();
}
return FibRecursive(n);
}
Потом уже добавить при необходимости аккумулятор-состояние в сигнатуру внутренней функции, проверки на API boundaries во внешней функции, etc. Именно так устроены стандартные рекурсивные ФВП в библиотеках всяких ФЯПов типа Хаскеля.
И выносить подобное «ядро» функции в скоуп класса — совсем моветон. Не говоря уже о том, что в такого рода рекурсивных функциях часто очень удобно замкнуть некоторые входящие параметры (неизменные от вызова к вызову) и не загромождать сигнатуру локальной функции.
S>Не, фигня получается. В результате в одном методе смешиваются две ответственности: реализация и собственно логика в виде наборов вызовов этой самой реализации.
Всё равно что сказать: в одном методе смешивается инициализация локальной переменной и её использование.
S>Ну, т.е. мы вернулись к ситуации, от которой пытались уйти рефакторингом на отдельные методы
Не вернулись. Те же отдельные методы, только инкапсулированы в скоупе. Никто же, надеюсь, не создаёт поля типа
private int _loopCounterForFooMethod;
private int _loopCounterForBarMethod;
private int _anotherLoopCounterForBarMethod;
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>То что лямбды являются замыканиями — для меня одна из главных фич, так как это позволяет сократить O(N) количество кода захвата до O(1).
Замечание верно, но меня пугают лямбды, у которых используется куча захваченных переменных.
Лично я смотрю на лямбды, как на удобные сокращения для очень маленьких функций, например в LINQ: list.OrderBy(x => x.Name);
Здравствуйте, Sinix, Вы писали:
S>>>Roslyn team завела обсуждение на local functions (объявление функции внутри функции). EP>>Это какие замыкания в C# по счёту? Третьи? S>Угу. Лямбды, итераторы/await, локальные функции.
"итераторы/await" — это всё же не замыкания. Под замыканием я понимаю прежде все захват переменных из внешнего scope.
Вообще, под первыми замыканиями я имел в виду: https://msdn.microsoft.com/en-us/library/0yw3tz5k.aspx
int n = 0;
Del d = delegate() { System.Console.WriteLine("Copy #:{0}", ++n); };
// A reference to the outer variable n is said to be captured when the delegate is created.
// Unlike local variables, the lifetime of a captured variable extends until the delegates
// that reference the anonymous methods are eligible for garbage collection.
Тут же нет захвата?
S>Если метод не вызывается миллионы раз. Каждый вызов — аллокация (если есть замыкания) + нет инлайнинга (в любом случае). S>>>3. Перфоманс. Создание лямбды на каждый вызов, и, главное, отсутствие инлайнинга — не лучший способ добавить хелпер-код. EP>>Казалось бы — почему бы не оптимизировать лямбды? S>Потому что для делегатов в принципе инлайнинг не сделаешь. Разве что реврайтом при компиляции, но это по общим затратам сложнее, чем добавить local functions
А почему для лямбд это трудно, а для local functions нет? В чём разница? Лямбды завязаны на какое-то ABI jit'а?
S>>>* рекурсия EP>>Пожалуй единственный аргумент за. S>Неа, уже приводили вариант с рекурсией и делегатами.
Какой вариант? С захватом переменной лямбды и разделением объявления и инициализации? Да, но это же workaround. Причём существуют и другие — например приём self в качестве параметра.
S>Тут нет одного конкретного сильного аргумента, только в комплексе перевешивает. S>В общем добавят — и фиг с ним. Обсуждение развели, как будто фича века
Если исправят скорость лямбд, то от этого выиграет уже существующий код.
Здравствуйте, Qbit86, Вы писали:
Q>Не уверен. Идеальный пример для local functions — рекурсивные функции.
За которые в шарпе на сегодня прилетает минус в премию, если они используются без веских на то оснований Ибо платформы разные, а вот StackOverflowException практически везде обрабатывается как corrupted state exception, т.е. пипец котёнку.
Для всякой мелочёвки "умерло в продакшне" некритично конечно, для более-менее приличного кода — фу-фу-фу. И давайте не будем про tail call opt под x64, про другие языки, умеющие в tail call, про "всегда можно написать тест" и тд и тп. Потому никакие баззворды для буллшит бинго, увы, не перевешивают опыта потери десятка-другого клиентов из-за дурацкого раздолбайства.
Q>Практически всегда сигнатура и предусловия вызова функции внешним клиентом отличается от самовызова функции в её реализации. ... Q>Потом уже добавить при необходимости аккумулятор-состояние в сигнатуру внутренней функции, проверки на API boundaries во внешней функции, etc. Именно так устроены стандартные рекурсивные ФВП в библиотеках всяких ФЯПов типа Хаскеля.
Одна проблема: если это делать — то делать для всех функций, не только для локальных. И тут разверзлась бездна звёзд полна, потому что решение для общего случая гарантирует Тьюринга за решение проблемы останова, для частного — проблему из прошлого абзаца не решает никак. Ибо совместимость.
Q>И выносить подобное «ядро» функции в скоуп класса — совсем моветон. Не говоря уже о том, что в такого рода рекурсивных функциях часто очень удобно замкнуть некоторые входящие параметры (неизменные от вызова к вызову) и не загромождать сигнатуру локальной функции.
Что при рефакторинге приводит к чему?
S>>Не, фигня получается. В результате в одном методе смешиваются две ответственности: реализация и собственно логика в виде наборов вызовов этой самой реализации.
Q>Всё равно что сказать: в одном методе смешивается инициализация локальной переменной и её использование.
Я выше привёл пример с настройкой КА. Что, серьёзно, вариант с "кишки в том же методе" будет смотреться лучше?
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, Qbit86, Вы писали:
Q>>Не уверен. Идеальный пример для local functions — рекурсивные функции. S>За которые в шарпе на сегодня прилетает минус в премию, если они используются без веских на то оснований Ибо платформы разные, а вот StackOverflowException практически везде обрабатывается как corrupted state exception, т.е. пипец котёнку.
Ага, именно, даж поймать где вылетело никак до сих пор. Они это фиксить так и не собираются вроде?
EP>"итераторы/await" — это всё же не замыкания. Под замыканием я понимаю прежде все захват переменных из внешнего scope.
Ну да, переменные(параметры) в итераторах и ко сохраняются между вызовами MoveNext()
EP>Вообще, под первыми замыканиями я имел в виду:
А, ну так анонимные делегаты и лямбды — эт одно и то же по большому счёту. Можно их по отдельности считать, тогда больше будет
S>>И, для ценителей, CreateDelegate + firstArgument: EP>Тут же нет захвата?
Угу, тут конечно не полноценный захват переменных, только значение. Строка, передаётся как первый параметр static-метода. Т.е. по сути получается аналог
EP>А почему для лямбд это трудно, а для local functions нет? В чём разница? Лямбды завязаны на какое-то ABI jit'а?
Неа, потому что лямбды — это делегаты, их значение может меняться в рантайме. Локальные функции ничем не отличаются от вызова приватного метода, для них отдельных приседаний делать не надо.
Даже для замыканий емнип всё ок, невиртуальные вызовы могут быть заинлайнены. Надо проверять конечно, могу наврать.
EP>Какой вариант? С захватом переменной лямбды и разделением объявления и инициализации? Да, но это же workaround. Причём существуют и другие — например приём self в качестве параметра.
Угу.
EP>Если исправят скорость лямбд, то от этого выиграет уже существующий код.
Там править нечего практически, стоимость уже сопоставима с вызовом без инлайнинга емнип. Где-то у меня был пример, надо будет найти.
Здравствуйте, agat50, Вы писали:
Q>>>Не уверен. Идеальный пример для local functions — рекурсивные функции. S>>За которые в шарпе на сегодня прилетает минус в премию, если они используются без веских на то оснований Ибо платформы разные, а вот StackOverflowException практически везде обрабатывается как corrupted state exception, т.е. пипец котёнку.
A>Ага, именно, даж поймать где вылетело никак до сих пор. Они это фиксить так и не собираются вроде?
С извращениями только. Или стек исключения (если сумели записать), или отладкой memory dump.
Меня в своё время это адски и неимоверно раздражало: любая, даже неявная рекурсия — потенциальная точка отказа для всего процесса. Блин.
Здравствуйте, Sinix, Вы писали:
S>Я выше привёл пример с настройкой КА. Что, серьёзно, вариант с "кишки в том же методе" будет смотреться лучше?
Из того, для некоторого примера фича неприменима, не следует, что фича неприменима в общем случае; тем более, что есть прмеры, когда она применима. Это очевидно. Если нет, просто не надо вступать со мной в дискуссию; я исхожу из того, что собеседник умеет в кванторы и силлогизмы.
S>За которые в шарпе на сегодня прилетает минус в премию, если они используются без веских на то оснований Ибо платформы разные, а вот StackOverflowException практически везде обрабатывается как corrupted state exception, т.е. пипец котёнку.
Если алгоритм рекурсивный, а функция нет, то ты всё равно получишь OutOfMemoryException, эмулируя стек вучную. Ты можешь протаскивать и ограничивать глубину вызова рекурсивной функции, или количество итераций в нерекурсивной функции типа «while (stack.Any()) { ... }».
S>Одна проблема: если это делать — то делать для всех функций, не только для локальных.
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, agat50, Вы писали:
Q>>>>Не уверен. Идеальный пример для local functions — рекурсивные функции. S>>>За которые в шарпе на сегодня прилетает минус в премию, если они используются без веских на то оснований Ибо платформы разные, а вот StackOverflowException практически везде обрабатывается как corrupted state exception, т.е. пипец котёнку.
A>>Ага, именно, даж поймать где вылетело никак до сих пор. Они это фиксить так и не собираются вроде? S>С извращениями только. Или стек исключения (если сумели записать), или отладкой memory dump. S>Меня в своё время это адски и неимоверно раздражало: любая, даже неявная рекурсия — потенциальная точка отказа для всего процесса. Блин.
Ну да, в дебаггере на продакшне очень удобно — иначе стек никак не поймать вроде, в unhandled для appdomain оно не долетает точно. Столкнулся на async void — очень уж понравилось некоторые вещи через рекурсию делать. И через пару недель работы тихая смерть. Хрен кстати вычистишь — решарпер все рекурсии не находит, только пофайлово, графы смотреть в 2012й ещё нельзя да и неявные не найти. Неужели это так сложно — добавить проверку в CLR на оставшийся стек и выбрасывть что-нибудь нормальное. На производительности никак не скажутся пара инструкций.
Причем самому это мониторить тоже нельзя — стектрейсы всех потоков не получить кроссплатформенно.
Здравствуйте, Qbit86, Вы писали:
Q>Из того, для некоторого примера фича неприменима, не следует, что фича неприменима в общем случае; тем более, что есть прмеры, когда она применима. Это очевидно. Если нет, просто не надо вступать со мной в дискуссию; я исхожу из того, что собеседник умеет в кванторы и силлогизмы.
Ок С возражениями согласен, свою точку зрения я уже высказал, так что закругляемся
S>>Одна проблема: если это делать — то делать для всех функций, не только для локальных. Q>Что делать для всех функций? При чём тут Тьюринг?
Ну низзя в общем случае для метода однозначно сказать: применима к нему tail call optimisation или нет.
Можно бросать предупреждения или молча не применять оптимизацию в ряде случаев, но тогда от исходной проблемы мы никуда не денемся.
Здравствуйте, Sinix, Вы писали:
S>>>Одна проблема: если это делать — то делать для всех функций, не только для локальных. Q>>Что делать для всех функций? При чём тут Тьюринг? S>Ну низзя в общем случае для метода однозначно сказать: применима к нему tail call optimisation или нет.
Я за наличие локальных (замыкающих) функций в языке, в том чсиле (но не только!) из-за возможности упрощения рекурсии — и в любом случае безотносительно tail call elimination. Такая оптимизация неплохой бонус, но если её нет, то это не повод отказываться ни от рекурсии, ни от локальных функций. Тьюринг не нужен. Через рекурсивные вызовы удобно организовывать, внезапно, рекурсивные алгоритмы. Быструю сортировку можно и без рекурсивных вызовов организовать, эмулировав стек, но так обычно не делают в стандартных библиотеках. То есть почему-то не спешат менять потенциальный StackOverflowEcxeption на потениальный OutOfMemoryException. Добавляют при необходимоти guard глубины рекурсии, и живут себе, поди, без штрафов к премиям.
В моей практике рекурсивные алгоритмы редко занимают больше нескольких десятков вложенных вызовов.
Здравствуйте, IT, Вы писали:
IT>Паскалевский опыт использования локальных функций у меня достаточно нейтральный. Т.е. ни плохо, ни хорошо. Нормальная удобная фича. Зато Немерловый опыт самый положительный.
Если так нравятся языки с упором на функциональное программирование, то почему бы не использовать их?
Здравствуйте, Sinix, Вы писали:
S>Ну низзя в общем случае для метода однозначно сказать: применима к нему tail call optimisation или нет. S>>>Одна проблема: если это делать — то делать для всех функций, не только для локальных.
Зачем непременно для всех? Если вдруг сделают для какого-то подмножества хвостовых вызовов, скажем, рекурсивных хвостовых вызовов (для них проще, чем в общем случае), то уже будет хорошо. Но к «локальным функциям» этот вопрос перпендикулярен, они удобны незавсимо от наличия оптимизации хвостовых вызовов, рекурсивных или нет.
Здравствуйте, Qbit86, Вы писали:
Q>Из того, для некоторого примера фича неприменима, не следует, что фича неприменима в общем случае; тем более, что есть прмеры, когда она применима. Это очевидно. Если нет, просто не надо вступать со мной в дискуссию; я исхожу из того, что собеседник умеет в кванторы и силлогизмы.
Это не повод замусоривать язык фичами, которые с высокой вероятностью в том же энтерпрайзе будут использоваться таким образом, что понизят читаемость и поддерживаемость кода.
Здравствуйте, Tesh, Вы писали:
T>Это не повод замусоривать язык фичами, которые с высокой вероятностью в том же энтерпрайзе будут использоваться таким образом, что понизят читаемость и поддерживаемость кода.
На мой взгляд, именованные замыкания с высокой вероятностью повысят читаемость и сопровождаемость кода.
Здравствуйте, Tesh, Вы писали:
IT>>Паскалевский опыт использования локальных функций у меня достаточно нейтральный. Т.е. ни плохо, ни хорошо. Нормальная удобная фича. Зато Немерловый опыт самый положительный. T>Если так нравятся языки с упором на функциональное программирование, то почему бы не использовать их?
На то есть много причин:
Во-первых, традиционные функциональные языки со своим синтаксисом — это зелёные человечки с другой планеты. Вроде бы как бы и всё логично, но совершенно анти-человечно.
Во-вторых, функциональная парадигма органично дополняет все остальные парадигмы включая ООП. Её место внутри метода, а снаружи весьма неплохо рулят другие. И хотелось бы воспользоваться всей мощью всех парадигм в одном языке. Великолепный пример такого гибридного языка — Немерле.
В-третьих, современный язык — это не только парадигма, синтаксис, библиотеки и фреймворки, но ещё и рабочее окружение ака Visual Studio или IntelliJ IDEA с полноценной поддержкой всего, а не только кривой подсветки синтаксиса.
На сегодняшний день, несмотря на многочисленные попытки, ни одного функционального языка, удовлетворяющего всем этим требованиями нет.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Qbit86, Вы писали:
Q>На мой взгляд, именованные замыкания с высокой вероятностью повысят читаемость и сопровождаемость кода.
Короткие функции типа лямбд, используемых в linq, не требуют именования, а более крупные, объявленные внутри другого метода, только увличат объем этого метода и ухудшат читаемость.