Roslyn team завела обсуждение на local functions (объявление функции внутри функции).
Пока обсуждение чисто техническое, комментарии от c# team/vb team выложат пожже. У кого есть желание — участвуйте
Зачем оно надо:
В принципе, особого смысла объявлять метод внутри метода нет. В большинстве случаев можно положить private-метод рядом, если припёрло —
Func<int> x = ()=> { return 42; };
...
var y = x();
и вперёд.
Официальные комментарии такие:
1. Упрощает код — не всегда очевидно, что функция — просто хелпер для одного конкретного метода и вызывать её не по делу не надо. 2. лямбды не умеют в yield return. Т.е. самый простой с валидацией аргументов превращается в
public IEnumerable<char> GetChars(int count)
{
if (count < 0) throw something;
return GetCharsCore(count);
}
private IEnumerable<char> GetCharsCore(int count)
{
for (int i = 0; i<count; i++) yield return'a';
}
вместо
public IEnumerable<char> GetChars(int count)
{
IEnumerable<char> GetCharsCore(int count)
{
for (int i = 0; i<count; i++) yield return'a';
}
if (count < 0) throw something;
return GetCharsCore(count);
}
3. Перфоманс. Создание лямбды на каждый вызов, и, главное, отсутствие инлайнинга — не лучший способ добавить хелпер-код.
4. Чтоб было совсем весело, в этого ужеежа запихнули фичи и от лямбд и от "обычных" методов:
* local functions могут вести себя как лямбды
* вывод типа для результата (если нет рекурсии)
* рекурсия (если не используется вывод типа. и не говорите мне, что это тоже рекурсия).
* захват переменных (aka lambda closures)
* ref/out — параметры, named args, default arg values etc.
S>В принципе, особого смысла объявлять метод внутри метода нет. В большинстве случаев можно положить private-метод рядом, если припёрло.
В Nemerle постоянно пользовался локальными функциями для всяких рекурсивных алгоритмов, работающих вместе подписчиков на события, и просто сложных методов (удобно сделать один набор проверок входных аргументов и в локальных функциях делать исключительно работу). Так что их добавление дело правильное.
Здравствуйте, Sinix, Вы писали:
S>Roslyn team завела обсуждение на local functions (объявление функции внутри функции).
Ну наконец-то.
S>
Зачем оно надо:
Альтернатив локальной функции две: лямбды и приватные хелперы. Лямбды в данном контексте инородны, выглядят криво, особенно рекурсивные, и не отличаются высокой производительностью. Приватные хелперы требуют передачи всего необходимого контекста, что легко порождает монстриков с количеством параметров 5+. К тому же приватные хелперы подталкивают неокрепшие умы к их повторному использованию (из разных методов). Тому повторному использованию, которое не только добро, но ещё и абсолютное, незамутнённое неосветлённое ни единой каплей добра, зло.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT> К тому же приватные хелперы подталкивают неокрепшие умы к их повторному использованию (из разных методов). Тому повторному использованию, которое не только добро, но ещё и абсолютное, незамутнённое ни единой каплей добра, зло.
а можно пример такого зла?
p.s. в лямбдах очень не хватает yield return, рекурсию в лямбдах ни разу не хотел. ну а так в целом всегда хотелось, конечно, чтобы класс не "засорять".
Здравствуйте, IT, Вы писали:
IT>Приватные хелперы требуют передачи всего необходимого контекста, что легко порождает монстриков с количеством параметров 5+.
У локальных функций свои беды. Если они являются одновременно замыканиями, то могут возникнуть коллизии с перекрытыми названиями параметров и внешних переменных (или параметров корневого метода), что может способствовать снижению читабельности. Да и вообще функция среди кода выглядит не очень. А локальные функции внутри локальных функций — это еще круче.
Понятно, что абузить можно любой механизм, но, ИМХО, локальные функции способствуют злоупотреблениям (из дельфийского опыта).
Здравствуйте, AlexRK, Вы писали:
ARK>Понятно, что абузить можно любой механизм, но, ИМХО, локальные функции способствуют злоупотреблениям (из дельфийского опыта).
Паскалевский опыт использования локальных функций у меня достаточно нейтральный. Т.е. ни плохо, ни хорошо. Нормальная удобная фича. Зато Немерловый опыт самый положительный.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Neco, Вы писали:
N>а можно пример такого зла?
// Безобидное начало.class A1
{
public void Method1()
{
var n = Helper("123");
}
private int Helper(string str)
{
var s = str + "456";
return s.Length;
}
}
// Эволюция кода I. Появляется ещё один шарильщик хелпера.class A2
{
public void Method1()
{
var n = Helper("123", false);
}
public void Method2()
{
var n = Helper("890", true);
}
private int Helper(string str, bool flag)
{
var s = str + (str.StartsWith("1") ? "456" : "654");
if (flag == true)
s = s.Substring(1);
return s.Length;
}
}
// Эволюция кода II. Появляется третий шарильщик хелпера.class A3
{
public void Method1()
{
var n = Helper("123", false, false, null);
}
public void Method2()
{
var n = Helper("890", true, false, null);
}
public void Method3()
{
var n = Helper("654", true, true, 3);
}
private int Helper(string str, bool flag1, bool flag2, int? p)
{
var s = str + (str.StartsWith("1") ? "456" : "654");
if (flag2 == true)
{
if (flag1 == true)
s = s.Substring(1);
}
else
{
if (flag1 == true || p == null)
s = s.Substring(2);
else
s = s.Substring(p.Value);
}
return s.Length;
}
}
// Код без эволюции, т.е. то, что получилось бы без использования повторного использования.class A4
{
public void Method1()
{
var n = 6;
}
public void Method2()
{
var n = 5;
}
public void Method3()
{
var n = 3;
}
}
N>p.s. в лямбдах очень не хватает yield return, рекурсию в лямбдах ни разу не хотел.
А мне очень не хватает локальной рекурсии, а вот локальный yield return ни разу не хотел, хотя использую его регулярно.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Sinix, Вы писали:
S>Roslyn team завела обсуждение на local functions (объявление функции внутри функции). S>Пока обсуждение чисто техническое, комментарии от c# team/vb team выложат пожже. У кого есть желание — участвуйте
S>
Зачем оно надо:
Что ещё весьма полезно — это возможность иметь локальные дженерик-функции
Здравствуйте, hardcase, Вы писали:
H>Зачем?
Потому что могут
Если серьзно, то начиная с шестого шарпа очевидно поменялся подход к отбору фич.
Классика "дизайним очередной big thing и вылизываем всё, что с ним связано" никуда не делась.
Но помимо неё в язык по мере возможности добавляется мелочёвка, которая почти не влияет на public API и которая не особенно нужна в энтерпрайзе (серьёзно, если в коде такой баррдак, что даже приватные методы использовать небезопасно, ну сделай ты рефакторинг!).
Зато эта мелочёвка будет полезна для всяких скриптов/однодневных поделок и прочего write-only кода.
Это ни хорошо, ни плохо, просто последствия попытки пролезть в мобильную нищу и в нишу инди-сайтов.
Здравствуйте, Sinix, Вы писали:
S>Зато эта мелочёвка будет полезна для всяких скриптов/однодневных поделок и прочего write-only кода.
За много лет написания всякого, в т.ч. и write-only кода, на языке, умеющем локальные функции, мне наверно один или два раза потребовалось иметь локальные generic функции. Речь именно о функциях в которых параметры типов описываются явно, а не наследуются от метода в который вложены.
Здравствуйте, hardcase, Вы писали:
H>За много лет написания всякого, в т.ч. и write-only кода, на языке, умеющем локальные функции, мне наверно один или два раза потребовалось иметь локальные generic функции. Речь именно о функциях в которых параметры типов описываются явно, а не наследуются от метода в который вложены.
Скорее всего с рослином проще дать генерик-функции, чем объяснять, почему нет.
UPD Как вариант, пригодится для скриптов и compiler as a service. Весь код запихиваем в одно тело метода и пусть оно себе компилируется, если сможет.
Здравствуйте, Sinix, Вы писали:
S>Скорее всего с рослином проще дать генерик-функции, чем объяснять, почему нет.
Под капотом поддержку генериков делать придется хотя бы потому, что такие параметры наследуются от метода, в который вложена локальная функция. Расширить сигнатуру до поддержки собственных генерик-параметров у локальной функции не трудно. Я говорю о том, что такого рода функции мало востребованы в реальной жизни.
Здравствуйте, hardcase, Вы писали:
S>>Скорее всего с рослином проще дать генерик-функции, чем объяснять, почему нет.
H>Под капотом поддержку генериков делать придется хотя бы потому, что такие параметры наследуются от метода, в который вложена локальная функция.
Там не так всё будет.
Для самого замыкания всё уже сделано.
А вот local generic-функция просто переедет в класс замыкания,
void A<T>(T x)
{
T2 DoSmth<T2>() { return SomeCode<T2>(x)};
DoSmth<int>();
}
// =>class Closure<T>
{
public T _x
public T2 DoSmth<T2>() { return SomeCode<T2>(_x)};
}
// ...void A<T>(T x)
{
var xyz = new Closure<T> { _x = x };
xyz.DoSmth<int>();
}
И вот объяснить, почему при переезде "генерики низзя" будет куда сложнее, чем собственно перенести.
Это ж надо задокументировать в стандарте, добавить диагностики/ошибки компилятора, добавить quick fix в студию, покрыть всё это дело тестами, написать на всё перечисленное документацию, пару статей и несколько глав в книжке по MS cert exam и тд и тп.
Здравствуйте, IT, Вы писали:
IT>Паскалевский опыт использования локальных функций у меня достаточно нейтральный. Т.е. ни плохо, ни хорошо. Нормальная удобная фича. Зато Немерловый опыт самый положительный.
Для того чтобы опыт использования локальных функций был однозначно положительным, крайне желательно, чтобы язык поддерживал оптимизацию хвостовых рекурсий. Иначе опыт, приобретенный в Немерле/Скале/ФШарпе, может сыграть плохую службу. Вроде локальные функции есть, но нормальную рекурсию на них не напишешь.
Здравствуйте, AngeL B., Вы писали:
AB>Для того чтобы опыт использования локальных функций был однозначно положительным, крайне желательно, чтобы язык поддерживал оптимизацию хвостовых рекурсий. Иначе опыт, приобретенный в Немерле/Скале/ФШарпе, может сыграть плохую службу. Вроде локальные функции есть, но нормальную рекурсию на них не напишешь.
В Немерле с этим всё в порядке.
Если нам не помогут, то мы тоже никого не пощадим.