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.
Здравствуйте, Sinix, Вы писали:
S>Roslyn team завела обсуждение на local functions (объявление функции внутри функции).
Ну наконец-то.
S>
Зачем оно надо:
Альтернатив локальной функции две: лямбды и приватные хелперы. Лямбды в данном контексте инородны, выглядят криво, особенно рекурсивные, и не отличаются высокой производительностью. Приватные хелперы требуют передачи всего необходимого контекста, что легко порождает монстриков с количеством параметров 5+. К тому же приватные хелперы подталкивают неокрепшие умы к их повторному использованию (из разных методов). Тому повторному использованию, которое не только добро, но ещё и абсолютное, незамутнённое неосветлённое ни единой каплей добра, зло.
Если нам не помогут, то мы тоже никого не пощадим.
S>В принципе, особого смысла объявлять метод внутри метода нет. В большинстве случаев можно положить private-метод рядом, если припёрло.
В Nemerle постоянно пользовался локальными функциями для всяких рекурсивных алгоритмов, работающих вместе подписчиков на события, и просто сложных методов (удобно сделать один набор проверок входных аргументов и в локальных функциях делать исключительно работу). Так что их добавление дело правильное.
Здравствуйте, Sinix, Вы писали:
VD>>Практика показала, что локальные функции помогает очистить и структурировать код. Так что мимо. S>Без обид, но нет. Скорее, получается копипаста и дублирование кода из-за того, что никто не знает, что подобный код уже написан.
Без обид, но да. Копипаста и дублирование — это когда копипаста и дублирование десятков и сотен строк кода. Копипаста 5-ти строчек кода — это не копипаста. Естественно, никто локальные фукнции в сотню строк кода писать не собирается. А вот для 5-ти строк — самое оно. Что получается при вынесении такого кода в приватный хелпер я тут уже демонстрировал.
Что касается ентерпрайзов, то лично для меня качественно реализованная бизнес логика — это прежде всего качественно изолированная бизнес логика от другой бизнес логики. Под этим следует понимать не всякие слои и прочие дробящие логику техники, а подход "вся логика в одном флаконе". Критерий качества очень простой — чем проще функциональный кусок извлекается из места дислокации без болезненных ощущений и оставшихся рудиментов, тем качество такого кода выше. Не трудно сообразить, что локальные функции при таком подходе всячески способствуют увеличению качества кода.
При работе на Немерле неоднократно наблюдалась (не только у меня одного) такая метаморфоза. Наплевав на догмы про размер метода, определённый функционал реализовывался внутри одного метода с широким использованием локальных фукнций. При достижении определённого, естественно субъективного, уровня дискомфорта, это метод вжик — и лёгким движением руки превращался в класс, где локальные функции становились приватными методами, а контекст метода распределялся по классу. В дальнейшем с одним из методов этого нового класса вполне могла произойти очередная подобная метаморфоза. Т.е. локальные функции при развитии функционала позволяют удерживать его вместе, а не дробить, что в дальнейшем способствует простоте преобразования такого функционала в более крупные компоненты.
Ну а грохнуть ненужный код без оставления рудиментов — так тут вообще много ума не надо.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Вообще мой поинт в следующем: EP>* В теле функций лучше всего себя показывает императивное программирование вкупе с некоторыми элементами характерными ФЯ — функции высшего порядка, лямбды/замыкания, и т.п. Добавление таких элементов не делает такой код функциональным — это всё тоже ИП с явным мутированием и итерациями. EP>* Интерфейсы же лучше всего делать функциональными и как можно более чистыми.
Функциональные интерфейсы?
EP>* Функции имеют больший приоритет нежели методы объектов. Акцент нужно делать на "ООП это один из поставщиков типов для параметров функций и их реализаций", а не на "реализации методов объектов используют статические-методы-помощники" (на что в своё время сделала акцент Java).
EP>То есть по сути — ФП снаружи, ИП с элементами ФЯ внутри, а ООП где-то сбоку.
Теперь понятно, что смущает в твоих рассуждениях. Фактически ты одни ФП фишки низводишь до уровня некоторых элементов характерных ФЯ, а другие возносишь до уровня определяющего парадигму и дальше делаешь далеко идущие выводы. В принципе, так можно договориться до того, что ФП — это ООП, а ООП — это ФП, т.к., например, достаточно выдержать средствами ООП или императивщиной чистоту функции и вот у нас уже не ООП, а чистейший ФП.
Давай подойдём к впоросу с другой стороны. Любая парадигма сопровождается практическими подходами, набором паттернов и инструментов. Если разобраться где это всё рулит лучше всего, то окажется, что:
* Императив внутри метода, как я уже говорил, рулит только говнокодом. Бедность императивных конструкций и подходов можно наблюдать в том же C# до 3-й версии. Объём некоторого императивного кода на следующих версиях шарпа иногда сокращается в разы при использовании новых языковых конструкций.
* Настоящее счастье внутри метода достигается только использованием инструментов и подходов из функционального программирования.
* Компоновка методов в более крупные образования делается с помощью ООП. Им же достигается усоверщенствование некоторых функциональных вещей типа АДТ. Сравни АДТ и ПМ в F# и Немерле. В F# это выглядит как убогая пародия. Надеюсь ПМ в C# будет похож более на Немерле, чем на C#.
Я кстати в плюсовом коде активно применяю лямбды в качестве локальных функций, и компилятор там, в отличие от .NET, не страдает формализмом головного мозга, а тупо инлайнит лямбды (как ИМХО и положено вести себя хорошему компилятору).
Здравствуйте, Sinix, Вы писали:
S>Roslyn team завела обсуждение на local functions (объявление функции внутри функции).
Отличная фича! Пожалуй самая часто используемая мной фича в Немерле.
S>Пока обсуждение чисто техническое, комментарии от c# team/vb team выложат пожже. У кого есть желание — участвуйте
S>
Зачем оно надо:
S>В принципе, особого смысла объявлять метод внутри метода нет.
Еще как есть!
S>В большинстве случаев можно положить private-метод рядом,
Локальная функция инкапсулирована внутри метода и не пересекается с другими. Приватные же методы, после некоторого количества, превращаются в лес.
Кроме того они позволяют делать замыкания (как лямбы) и могут быть безопасно оптимизированы (инлайнены).
S>если припёрло — S>
S>Func<int> x = ()=> { return 42; };
S>...
S>var y = x();
S>
S>и вперёд.
При этом создается делегат, который часто не нужен. Да и криво. Особо криво, если нужна рекурсия:
cs]
Func<int> x = null;
x = ()=> { return x(); };
...
var y = x();
S>Официальные комментарии такие:
S>1. Упрощает код — не всегда очевидно, что функция — просто хелпер для одного конкретного метода и вызывать её не по делу не надо.
Подтверждаю.
S>3. Перфоманс. Создание лямбды на каждый вызов, и, главное, отсутствие инлайнинга — не лучший способ добавить хелпер-код.
+1
S>4. Чтоб было совсем весело, в этого ужеежа запихнули фичи и от лямбд и от "обычных" методов: S>* local functions могут вести себя как лямбды S>* вывод типа для результата (если нет рекурсии)
В правильных языках вывод типов полный не зависящий от рекурсии. Рекурсия ничем не мешает выводу типов.
S>* рекурсия (если не используется вывод типа. и не говорите мне, что это тоже рекурсия).
Ничего не понял. Какие проблемы с рекурсией и выводе типов?
S>* захват переменных (aka lambda closures) S>* ref/out — параметры, named args, default arg values etc.
ref/out в принципе не нужны. Есть же замыкания. В прочем, больше не меньше.
S>Как-то так.
Все правильно. Но такое ощущение, что ты имеешь что-то против локальных функций?
ЗЫ
Чую, еще лет через 10 Шарпа таки станет Немерлом.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, AlexRK, Вы писали:
ARK>Понятно, что абузить можно любой механизм, но, ИМХО, локальные функции способствуют злоупотреблениям (из дельфийского опыта).
Паскалевский опыт использования локальных функций у меня достаточно нейтральный. Т.е. ни плохо, ни хорошо. Нормальная удобная фича. Зато Немерловый опыт самый положительный.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Qbit86, Вы писали:
Q>Не уверен. Идеальный пример для local functions — рекурсивные функции.
За которые в шарпе на сегодня прилетает минус в премию, если они используются без веских на то оснований Ибо платформы разные, а вот StackOverflowException практически везде обрабатывается как corrupted state exception, т.е. пипец котёнку.
Для всякой мелочёвки "умерло в продакшне" некритично конечно, для более-менее приличного кода — фу-фу-фу. И давайте не будем про tail call opt под x64, про другие языки, умеющие в tail call, про "всегда можно написать тест" и тд и тп. Потому никакие баззворды для буллшит бинго, увы, не перевешивают опыта потери десятка-другого клиентов из-за дурацкого раздолбайства.
Q>Практически всегда сигнатура и предусловия вызова функции внешним клиентом отличается от самовызова функции в её реализации. ... Q>Потом уже добавить при необходимости аккумулятор-состояние в сигнатуру внутренней функции, проверки на API boundaries во внешней функции, etc. Именно так устроены стандартные рекурсивные ФВП в библиотеках всяких ФЯПов типа Хаскеля.
Одна проблема: если это делать — то делать для всех функций, не только для локальных. И тут разверзлась бездна звёзд полна, потому что решение для общего случая гарантирует Тьюринга за решение проблемы останова, для частного — проблему из прошлого абзаца не решает никак. Ибо совместимость.
Q>И выносить подобное «ядро» функции в скоуп класса — совсем моветон. Не говоря уже о том, что в такого рода рекурсивных функциях часто очень удобно замкнуть некоторые входящие параметры (неизменные от вызова к вызову) и не загромождать сигнатуру локальной функции.
Что при рефакторинге приводит к чему?
S>>Не, фигня получается. В результате в одном методе смешиваются две ответственности: реализация и собственно логика в виде наборов вызовов этой самой реализации.
Q>Всё равно что сказать: в одном методе смешивается инициализация локальной переменной и её использование.
Я выше привёл пример с настройкой КА. Что, серьёзно, вариант с "кишки в том же методе" будет смотреться лучше?
Здравствуйте, 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;
Здравствуйте, Sinix, Вы писали:
S>Без обид, но нет. Скорее, получается копипаста и дублирование кода из-за того, что никто не знает, что подобный код уже написан. Разумеется, это справедливо только для проектов, в которых один человек в принципе не сможет охватить всю codebase.
В приватные методы, выносят ровно тот же код и никто не планирует его использовать еще где-то в проекте. То есть озвученная тобой проблема отказом от локальных функций в пользу приватных методов не решается никак.
В локальные функции выносится тот код, которому по всем признакам (кроме ограничения на количество строк) место в том же методе. А в ограничение по количеству строк, код локальных функций попадать и не должен.
Кстати, самые большие грабли в энтерпрайз коде я встречал от ложного понимания, что подобный код уже написан. IT уже привел хорошие, хотя и слегка гротескные примеры, когда похожий внешне код реализует разные бизнес алгоритмы. При эволюционных изменениях этих алгоритмов, код превращается в страшного монстра.
Ответом на эту проблему обычно все называют рефакторинг. Но я предпочитаю сразу не бросаться с шашкой похожести кода на вынесение подобного кода в различные фреймворки. А рефакторинг оставляю как раз на тот случай, когда код действительно один и тот же и его можно вынести из разных методов. Этот рефакторинг (в отличии от разделения сросшихся перепараметризованных алгоритмов) легко детектируется и прост как три копейки (если с ним возникают сложности, то лишь это еще один полезный индикатор его целесообразности).
UPD: не сразу понял, что делаю некропостинг и не прочитал всю ветку ) некоторые вещи уже говорились другими авторами.
Здравствуйте, IT, Вы писали:
IT>Копипаста и дублирование — это когда копипаста и дублирование десятков и сотен строк кода. Копипаста 5-ти строчек кода — это не копипаста.
Критерий всё-таки другой. Дублирование — это такая копипаста, которая приводит к аномалиям редактирования. Если вместо вынесения общего функционала (в том числе пятистрочного) я его копипащу, то при редактировании мне нужно поправить все места пасты, это чреватое ошибками дублирование.
Если копипаста приводит к созданию новых независимых экзмпляров кода, то это не дублирование, это временное кажущееся визуальное совпадение разных частей кода. Изменение одной части не должно приводить к изменению другой части.
Например, у меня функция работает с коллекцией элементов и внутри отбирает «квадратные» и «синие». Я могу либо завести локальную функцию для фильтра, либо (если не требуется замыкание) вынести в приватный метод-хелпер. Если моему коллеге нужен аналогичный фильтр, то во втором случае он его может переиспользовать; а в первом случае реализует повторно или копипастит.
Вот только в случае вложенной функции я всегда волен при необходимости поменять фильтр и отбирать ещё и «теплые» элементы. В случае расшаренного функционала я могу случайно либо сломать тех пользователей фильтра, которым нужны не только «тёплые», либо мне придётся «отдать» владение своим фильтром и писать новый, ни от кого (пока) не зависящий.
В общем все те твои рассуждение про изолированность частей кода, про инкапсуляцию и локальность.
Здравствуйте, 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>В общем добавят — и фиг с ним. Обсуждение развели, как будто фича века
Если исправят скорость лямбд, то от этого выиграет уже существующий код.
Здравствуйте, Sinix, Вы писали:
S>О! А можно подробности?
Я деталей не помню, но помню, что там было все не просто. Если не ошибаюсь, проблемы возникают кода дженерик-фукции вложены в другие дженерики (имеют неявный захват дженерик-параметров).
S>Ну а зачем в типовом скрипте что-то кроме локальных функций? Плюс, если вводить методы — клиентам надо будет прописывать сигнатуру основного метода.
Скрипт, скрипту рознь. В общем, случае скрипт может отличаться от серьезного приложения только тем, что его запускают как скрипт.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
EP>Вообще мой поинт в следующем: EP>* В теле функций лучше всего себя показывает императивное программирование вкупе с некоторыми элементами характерными ФЯ — функции высшего порядка, лямбды/замыкания, и т.п. Добавление таких элементов не делает такой код функциональным — это всё то же ИП с явным мутированием и итерациями.
Нашёл статью на эту тему с говорящим названием: Higher Order Imperative Programming.
Kershenbaum, Musser, Stepanov.
1988 год между прочим.
http://www.stepanovpapers.com/hop.pdf
...
There is usually a further restriction to a purely applicative (no side effects) style of programming.
...
we recognize that the use of higher order techniques is orthogonal to the question of applicative vs non-applicative styles.
Higher order techniques can in fact be used with great effectiveness with imperative forms and mutative procedures, and hence can be put into much more widespread use today, in existing languages, without need for new language or architectual advances.
Здравствуйте, 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>
Зачем оно надо:
Что ещё весьма полезно — это возможность иметь локальные дженерик-функции
Здравствуйте, IT, Вы писали:
IT>Приватные хелперы требуют передачи всего необходимого контекста, что легко порождает монстриков с количеством параметров 5+.
У локальных функций свои беды. Если они являются одновременно замыканиями, то могут возникнуть коллизии с перекрытыми названиями параметров и внешних переменных (или параметров корневого метода), что может способствовать снижению читабельности. Да и вообще функция среди кода выглядит не очень. А локальные функции внутри локальных функций — это еще круче.
Понятно, что абузить можно любой механизм, но, ИМХО, локальные функции способствуют злоупотреблениям (из дельфийского опыта).
Здравствуйте, IT, Вы писали:
IT>Паскалевский опыт использования локальных функций у меня достаточно нейтральный. Т.е. ни плохо, ни хорошо. Нормальная удобная фича. Зато Немерловый опыт самый положительный.
Для того чтобы опыт использования локальных функций был однозначно положительным, крайне желательно, чтобы язык поддерживал оптимизацию хвостовых рекурсий. Иначе опыт, приобретенный в Немерле/Скале/ФШарпе, может сыграть плохую службу. Вроде локальные функции есть, но нормальную рекурсию на них не напишешь.
Здравствуйте, VladD2, Вы писали:
VD>Ничего не понял. Какие проблемы с рекурсией и выводе типов?
Для лямбд вывод сделан, технических проблем показать ошибку для экзотики типа "X() { return X() — 1; }" тоже нет. Это только начало обсуждения — напиши им, может добавят.
VD>Все правильно. Но такое ощущение, что ты имеешь что-то против локальных функций?
Неа, есть — ну и фиг с ними. Другое дело, что в нормальном коде локальные функции особо не нужны. Если бардак дошёл до состояния "даже приватных методов не хватает для инкапсуляции" — надо код чинить, а не локальные функции прикручивать.
Но это, разумеется, только для моих сценариев использования. Для всякой тяжёлой инфраструктурщины типа внутренностей компилятора, уверен, всё наоборот
___
Чтоб не писать кучу ответов, тут же:
___
S>>Но помимо неё в язык по мере возможности добавляется мелочёвка, которая почти не влияет на public API и которая не особенно нужна в энтерпрайзе (серьёзно, если в коде такой баррдак, что даже приватные методы использовать небезопасно, ну сделай ты рефакторинг!). VD>Звучит так, что этот ваш энтерпрайз — это что-то очень плохое рассчитанное на участников специальных олимпиад.
Неа. Энтерпрайз — это когда код написанный один раз живёт минимум 5-10 лет и живёт не "один раз написано — всегда работает", а периодически меняется, потому что логика, пожелания заказчиков и законодательство не сочетаются практически никогда.
При этом объём этого кода (точнее, объём пожеланий заказчиков, которых не один-два мелких, а несколько сотен крупных) в принципе не позволяет повторить написанное силами одной команды (читай 5-10 человек) за разумный срок. А каждая строчка в биз-логике (только в ней, про инфраструктуру не говорим) — предметом долгих обсуждений и потенциальной причиной эпичных косяков у клиентов с вполне материальными последствиями. Типа увольнений и визита прокуратуры/налоговой из-за ошибки в коде восьмилетней давности. Т.е. код внезапно начинает иметь ценность.
Большинство "комнатных" подходов к разработке, для которых вполне справедливо "мы принимаем логичные решения", "этот код можно переписать", "всегда можно найти автора и узнать почему", "ошибку легко исправить" в таких условиях загнутся на втором-третьем году существования проекта. Да и команда разбежится нафиг, потому что никому не нравится копаться в мусорном коде.
Поэтому или пишем сразу короткий, понятный, поддерживаемый код, или проект очень быстро разрастается до состояния, когда внедрение даже самой малой фигни требует согласованной работы нескольких человек. Что сразу даёт 300% к срокам и -100 к морали команды.
VD>Практика показала, что локальные функции помогает очистить и структурировать код. Так что мимо.
Без обид, но нет. Скорее, получается копипаста и дублирование кода из-за того, что никто не знает, что подобный код уже написан. Разумеется, это справедливо только для проектов, в которых один человек в принципе не сможет охватить всю codebase.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Это какие замыкания в C# по счёту? Третьи? EP>Это и так умеет лямбда.
Я бы вместо добавления локальных функций урезал лямбды — запретил им быть замыканиями и содержать больше одного выражения.
С целью слегка ограничить полет фантазии особо одаренных.
Правда, уверен, в текущем обсуждении меня мало кто поддержит.
Здравствуйте, Sinix, Вы писали:
S>>>Одна проблема: если это делать — то делать для всех функций, не только для локальных. Q>>Что делать для всех функций? При чём тут Тьюринг? S>Ну низзя в общем случае для метода однозначно сказать: применима к нему tail call optimisation или нет.
Я за наличие локальных (замыкающих) функций в языке, в том чсиле (но не только!) из-за возможности упрощения рекурсии — и в любом случае безотносительно tail call elimination. Такая оптимизация неплохой бонус, но если её нет, то это не повод отказываться ни от рекурсии, ни от локальных функций. Тьюринг не нужен. Через рекурсивные вызовы удобно организовывать, внезапно, рекурсивные алгоритмы. Быструю сортировку можно и без рекурсивных вызовов организовать, эмулировав стек, но так обычно не делают в стандартных библиотеках. То есть почему-то не спешат менять потенциальный StackOverflowEcxeption на потениальный OutOfMemoryException. Добавляют при необходимоти guard глубины рекурсии, и живут себе, поди, без штрафов к премиям.
В моей практике рекурсивные алгоритмы редко занимают больше нескольких десятков вложенных вызовов.
Здравствуйте, Tesh, Вы писали:
T>Это не повод замусоривать язык фичами, которые с высокой вероятностью в том же энтерпрайзе будут использоваться таким образом, что понизят читаемость и поддерживаемость кода.
На мой взгляд, именованные замыкания с высокой вероятностью повысят читаемость и сопровождаемость кода.
Немерле, как я понял, больше функциональный, чем объекто-ориентированный, поэтому больший уклон в функциональщину там может смотреться вполне логичным.
C# же скорее наоборот.
Что мне не нравится в подобных попытках перенести все возможные фичи в один язык, так это то, что это не способствует единообразию кода между проектами, и получается очередной C++ с многообразием способов прострелить себе ногу.
Как разультат, легко может случиться так, что в проекте, который передадут на развитие, будет человек со своим видением использования тех же локальных функций, что приведет к огромному количеству нечитаемого тяжело поддерживаемого кода.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>У детерминированности и отсутствии побочных эффектов действительно более широкое значение чем у pure function. Тем не менее, когда наш код целиком состоит из pure functions — то да, мы имеем дело с ФП, как минимум с чем-то крайне близким к ФП — это следует из самого определения ФП.
В подобных рассуждениях мне видится определённый косяк. Особенно учитывая, что код, состоящий из одних чистых функций по своей сути бесполезен именно по причине отсутствия побочных эффектов, как раз ради которых и создаются приложения.
Получается, что:
1. На любом языке можно писать в функциональном стиле, таким образом любой язык является функциональным.
2. Т.к. чистый функциональный язык по сути бесплезен, то любой язык, на котором можно писать в функциональном стиле бесполезен.
3. Таким образом функциональный стиль бесполезен.
Далее можно договорится до чего угодно, собенно учитывая, что на любом языке можно писать не только в чистом функциональном стиле, но и в чистом ОО. Таким образом твои чистые рассуждения ведут в никуда.
IT>>Тем не менее до недавнего времени они встречались только либо в ФЯ, либо в гибридах. EP>Лямбды-замыкания были в ООП-шном Smalltalk, а ФВП были даже в императивном C.
Тем более они всегда были в ассемблере.
IT>>Достаточно написать метод, который умножет два на три и вот уже оно ФП в полный рост. EP>А что смущает?
Смущает весьма вольное обобщение.
EP>Несколько раз повторяешь что ИП внутри метода плох. Хотелось бы услышать аргументы, так как по моим представлением именно ИП (пусть и разбавленный фишками исторически характерными скорее ФЯ) сейчас лучше всего подходит для тел функций.
Аргументы здесь приводились уже миллионы раз. Кроме как скупостью весьма грубых доступных средств ИП больше ничем не выделяется. Три вида цикла, switch, if, break, return, да ещё чуток по мелочи. Вот и весь ИП. Честно говоря, мне вообще странно слышать подобное мнение в 2015-м году.
EP>Думаю что нет — для полного ФП нужны и ФП интерфейсы и ФП внутренности.
ФП интерфейсов не бывает. Это уже какая-то отсебятина.
EP>А при том что компоновка это про модули и namespace'ы, а не про ООП и классы.
Компонентность — это естественное развитие ООП. ФП к этому уж точно никакого отношения не имеет.
EP>Кстати, эти статические классы ведь даже импортировать нельзя в C#
Этого утверждения не понял.
EP>Насколько я понял под "ФП рулит внутри" — ты понимаешь именно ИП приправленное щепоткой фишек ФЯ — так? EP>Или ты имеешь в виду именно "ФП внутри" в чистом "Haskell'овском" смысле?
В любых смыслах. Я вовсе не против ИП, но как только внутрь метода приходит ФП, то ИП остаётся только смущённо покуривать в сторонке и ждать пока его позовут. Ни о каком рулении речи больше не идёт.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
T>>Как результат, время вхождения в проект только увеличится.
IT>Это старая песня закалённого в интригах менеджера про пучок индусов вместо одного вменяемого разработчика. Тут есть только одна проблема — функция Sum для IQ не работает. Работает только функция Max. Тренеруйте своих разработчиков, гоните в шею бездарей и всё у вас получится. Посредственности могут выдавать только посредственный результат.
Как показывает практика, в большинстве случаев — Max / n, для n > 3
Если при компиляции и исполнении вашей программы не происходит ни одной ошибки — это ошибка компилятора :)))
Здравствуйте, Sinix, Вы писали:
S>Скорее всего с рослином проще дать генерик-функции, чем объяснять, почему нет.
На самом деле реализовать дженерик-локальнфе функции сложнее. Там есть подводные грабли.
S>UPD Как вариант, пригодится для скриптов и compiler as a service. Весь код запихиваем в одно тело метода и пусть оно себе компилируется, если сможет.
Зачем?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, ionoy, Вы писали:
I>В первых, о приватных методах тоже легко забыть и переопределить аналогичную логику в другом месте.
Это постараться надо. Методы обычно называются логично, поиск студии или решарпера (который go to symbol, показывает подходящие методы в реалтайме) находит кандидатов на раз-два. Не, поддержка приватных методов тоже добавится со временем, но во-первых там больше шансов наткнуться на непонятное имя, во-вторых, смысл в разделении теряется окончательно.
I>Зато локальные функции дают +10 к читаемости и поддерживаемости кода. Сравни:
Ну тогда и сравнивать надо боль-менее реалистичное
public string DoStuff(string text, string text2)
{
if (ExtractInfo(text) == "HELLO") return ExtractInfo(text2);
...
if (ExtractInfo2(text) == "HELLO") return ExtractInfo2(text2);
...
}
private string ExtractInfo(string text)
{
...
...
...
}
private string ExtractInfo2(string text)
{
...
...
...
...
...
}
// иpublic string DoStuff(string text, string text2)
{
var extractInfo = (s) => ....;
{
...
...
...
}
var extractInfo2 = (s) => ....;
{
...
...
...
...
...
}
if (ExtractInfo(text) == "HELLO") return ExtractInfo(text2);
...
if (ExtractInfo2(text) == "HELLO") return ExtractInfo2(text2);
...
}
а не надуманный пример. Смысл растаскивать методы-локальные хелперы от метода, в котором они используются?
И в таком виде первый вариант выигрывает без вариантов. Т.е. первый же отрыв от сфероконины и переход к реальному коду — уже упс.
И да, для студии с peek definition вся эта возня с "в каком порядке расположено" особого смысла не имеет. Щёлкнул alt-f12 — увидел рядом реализацию, при необходимости поправил. Попробуйте, это одна из самых недооценённых фишек vs2013.
Здравствуйте, ionoy, Вы писали:
I>Спорить не буду, сам всё увидишь, если всё-таки добавят.
Ок
S>>И да, для студии с peek definition вся эта возня с "в каком порядке расположено" особого смысла не имеет. I>Это да, никак не могу заставить себя ей пользоваться, несмотря на явную полезность. Но чтобы подправить код, в любом случае придётся перемещаться.
Неа, правится прямо в peek window, в том-то и прелесть.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
IT>>У детерминированности и отсутствии побочных эффектов тоже более широкое значение чем pure function в фукциональном программировании. Тем не менее тебе это не мешает делать вывод, что если функция удовлетворяет этим двум условиям, то мы однозначно имеем дело с ФП
EP>У детерминированности и отсутствии побочных эффектов действительно более широкое значение чем у pure function. Тем не менее, когда наш код целиком состоит из pure functions — то да, мы имеем дело с ФП, как минимум с чем-то крайне близким к ФП — это следует из самого определения ФП.
pure function накладывает ограничение на наблюдаемое извне поведение функции, а не на то, в какой парадгиме она написана внутри. Ровно поэтому "когда наш код целиком состоит из pure functions" — не гарантирует близости к ФП, т.к. тела функций могут быть 100% императивны.
Пример
int main()
{
// 1000 строк кода ЧИСТОГО императива, вычисляющего число корней уравнения f(x)=1, и возвращающая его.return n;
}
IT>>А мужики-то и не знают. Оказывается я с самого первого дня писал в функциональном стиле. Особенно на Фортране и PL/1. Ага.
EP>Если не было никакого явного мутирования данных (в чём я сомневаюсь), функции были чистыми — то да, это был функциональный стиль.
Чистота функций накладывает ограничение на наличие явного мутирования данных только в том случае, когда данные могут быть восприняты в качестве побочного эффекта.
IT>>Но, как я понимаю выдерживание принципов ФП снаружи не взирая на любое ИП внутри легко делают методы функциональными?
EP>Думаю что нет — для полного ФП нужны и ФП интерфейсы и ФП внутренности.
То есть выходит что разница между полным ФП и "чем-то крайне близким к ФП" во внутренностях? Выходит, что внутренности крайне незначительны? Какую ширину имеет эта крайняя близость?
Здравствуйте, IT, Вы писали:
EP>>У детерминированности и отсутствии побочных эффектов действительно более широкое значение чем у pure function. Тем не менее, когда наш код целиком состоит из pure functions — то да, мы имеем дело с ФП, как минимум с чем-то крайне близким к ФП — это следует из самого определения ФП. IT>В подобных рассуждениях мне видится определённый косяк. Особенно учитывая, что код, состоящий из одних чистых функций по своей сути бесполезен именно по причине отсутствия побочных эффектов, как раз ради которых и создаются приложения.
Разнообразный ввод/вывод описывается чистыми функциями (и трёхэтажными замыканиями). Haskell — чистый ФП язык, и на нём можно создавать полезные приложения в которых все функции будут чистыми.
полностью чистая, сама не делает никакого IO, но runtime может использовать её результирующее значение как угодно, в том числе сделать eval. Примерно таким образом (опять же, слишком упрощённо) и строится ввод/вывод на чистых функциях.
IT>Получается, что: IT>1. На любом языке можно писать в функциональном стиле,
Допустим.
IT>таким образом любой язык является функциональным.
Не думаю что это достаточное условие.
IT>2. Т.к. чистый функциональный язык по сути бесплезен,
Неверно.
IT>то любой язык, на котором можно писать в функциональном стиле бесполезен.
Вдвойне неверно. Даже если допустить что ФЯ бесполезен, из этого не следует что язык, в своём подмножестве допускающий ФП, является бесполезным.
IT>>>>>Теперь понятно, что смущает в твоих рассуждениях. Фактически ты одни ФП фишки низводишь до уровня некоторых элементов характерных ФЯ, EP>>>>Каковыми они и являются. Отсутствие тех или иных фишек в языках которые называют функциональными, плюс присутствие таких фишек даже в старых императивных языках, как раз и говорит о том что эти фишки не определяющие для ФП. IT>>>Тем не менее до недавнего времени они встречались только либо в ФЯ, либо в гибридах. EP>>Лямбды-замыкания были в ООП-шном Smalltalk, а ФВП были даже в императивном C. IT>Тем более они всегда были в ассемблере.
Тем более. Это только подтверждает выделенное.
IT>>>Достаточно написать метод, который умножет два на три и вот уже оно ФП в полный рост. EP>>А что смущает? IT>Смущает весьма вольное обобщение.
Вольное обобщение это использование термина "ФП" для описания ИП с примесью некоторых фишек характерных для ФЯ, но их не определяющих.
EP>>Несколько раз повторяешь что ИП внутри метода плох. Хотелось бы услышать аргументы, так как по моим представлением именно ИП (пусть и разбавленный фишками исторически характерными скорее ФЯ) сейчас лучше всего подходит для тел функций. IT>Аргументы здесь приводились уже миллионы раз. Кроме как скупостью весьма грубых доступных средств ИП больше ничем не выделяется. Три вида цикла, switch, if, break, return, да ещё чуток по мелочи.
+ Изменяемые массивы, да и вообще изменяемые структуры данных
+ ФВП (типа std::transform, std::partition_point, std::stable_partition, etc)
+ Лямбды-замыкания, в том числе изменяемые
+ ...
IT>Вот и весь ИП.
Ты сужаешь ИП до чего-то типа C семидесятых годов. ИП это Императивное Программирование, а не какой-то фиксированный набор циклов и условных операторов.
IT>Честно говоря, мне вообще странно слышать подобное мнение в 2015-м году.
Я же не говорю что нужно использовать только "три вида цикла, и т.п.".
Есть же куча удобных дополнений типа параметрического полиморфизма, вывода типов и т.п., но при этом не превращающие ИП в ФП.
Например — C++1998 STL (алгоритмы и структуры данных) — это ИП в полный рост, как снаружи, так и внутри. Свои задачи прекрасно решает, ФП бы так не справилось.
И вот подобный стиль, по моим представлением, лучше всего подходит для тел функций.
EP>>А при том что компоновка это про модули и namespace'ы, а не про ООП и классы. IT>Компонентность — это естественное развитие ООП. ФП к этому уж точно никакого отношения не имеет.
Ранее шёл разговор про компоновку, а не про компонентность.
EP>>Кстати, эти статические классы ведь даже импортировать нельзя в C# IT>Этого утверждения не понял.
Использование статических методов без указания имени класса. Например есть в Java.
EP>>Насколько я понял под "ФП рулит внутри" — ты понимаешь именно ИП приправленное щепоткой фишек ФЯ — так? EP>>Или ты имеешь в виду именно "ФП внутри" в чистом "Haskell'овском" смысле? IT>В любых смыслах.
Судя потому что чистый ФП ты считаешь бесполезным — то точно не в "Haskell'овском" смысле.
IT>Я вовсе не против ИП, но как только внутрь метода приходит ФП, то ИП остаётся только смущённо покуривать в сторонке и ждать пока его позовут.
Может конкретный пример? Как выглядит приход ФП во внутрь метода? Что появляется, что исчезает?
Здравствуйте, Sinix, Вы писали:
S>Зато эта мелочёвка будет полезна для всяких скриптов/однодневных поделок и прочего write-only кода.
За много лет написания всякого, в т.ч. и write-only кода, на языке, умеющем локальные функции, мне наверно один или два раза потребовалось иметь локальные generic функции. Речь именно о функциях в которых параметры типов описываются явно, а не наследуются от метода в который вложены.
Здравствуйте, Qbit86, Вы писали:
Q>Локальные функции — это вопрос скоупа. Просто найди все обычные рекомендации из C++/C# о том, что надо объявляеть локальные переменные ближе к их использованию, и зачем ограничивать их области видимости. А потом примени эти рекомендации к локальным функциям.
Дело в том, что у меня есть опыт использования чего-то похожего на практике. Причём в случае, где без делегатов/локальных функций особо не обойтись. Штука с кучей магии под капотом, что-то типа динамически генерируемого КА с автоматическим разруливанием циклов/зависимостей. Казалось бы, идеальный пример для local functions.
Аля
void Handle(Something y)
{
Rule(x => x.Sum > 100, x => { x.Sum = 100; });
IfChanged(x => x.Sum, x => { x.WorkLimit = WorkFromScale(x.Sum); });
IfChanged(x => x.WorkLimit, x => FixWorked(x));
IfChanged(x => x.Worked, x => FixSum(x));
Run(y);
}
Так вот, несмотря на то, что в принципе все эти WorkFromScale(), FixWorked(), FixSum() etc состояли из одной-двух строчек, на практике никто и никогда добровольно не записывал их прямо по месту использования. Хотя хелпер для этого был предусмотрен.
Потому что с точки зрения логики конкретное действие и настройка workflow — это принципиально разные вещи. И нефиг их смешивать в один метод.
Q>Выносить локальные функции в скоуп класса так же неприятно, как выносить локальные переменные алгоритма в поля класса.
Не, фигня получается. В результате в одном методе смешиваются две ответственности: реализация и собственно логика в виде наборов вызовов этой самой реализации. Ещё и с кучей неявных зависимостей из-за замыканий.
Ну, т.е. мы вернулись к ситуации, от которой пытались уйти рефакторингом на отдельные методы
Q>В VSCode аналогичная фича тоже приятно реализована.
+1.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
S>>Roslyn team завела обсуждение на local functions (объявление функции внутри функции). EP>Это какие замыкания в C# по счёту? Третьи?
Угу. Лямбды, итераторы/await, локальные функции. И, для ценителей, CreateDelegate + firstArgument:
public static void Do(string a, int b)
{
for (int i = 0; i < b; i++)
{
Console.WriteLine(a);
}
}
static void Main()
{
Action<int> a = (Action<int>)Delegate.CreateDelegate(
typeof(Action<int>),
"hello",
typeof(Program).GetMethod("Do"));
a(10);
Console.Write("Done.");
Console.ReadKey();
}
S>>1. Упрощает код — не всегда очевидно, что функция — просто хелпер для одного конкретного метода и вызывать её не по делу не надо. EP>Для этого достаточно лямбды.
Если метод не вызывается миллионы раз. Каждый вызов — аллокация (если есть замыкания) + нет инлайнинга (в любом случае).
S>>2. лямбды не умеют в yield return. Т.е. самый простой с валидацией аргументов превращается в EP>В чём проблема добавить?
S>>3. Перфоманс. Создание лямбды на каждый вызов, и, главное, отсутствие инлайнинга — не лучший способ добавить хелпер-код. EP>Казалось бы — почему бы не оптимизировать лямбды?
Потому что для делегатов в принципе инлайнинг не сделаешь. Разве что реврайтом при компиляции, но это по общим затратам сложнее, чем добавить local functions
S>>4. Чтоб было совсем весело, в этого ужеежа запихнули фичи и от лямбд и от "обычных" методов: S>>* local functions могут вести себя как лямбды EP>В каком смысле?
Следы редактирования Лишний пункт
S>>* рекурсия EP>Пожалуй единственный аргумент за.
Неа, уже приводили вариант с рекурсией и делегатами. Тут нет одного конкретного сильного аргумента, только в комплексе перевешивает.
В общем добавят — и фиг с ним. Обсуждение развели, как будто фича века
Здравствуйте, 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>Из того, для некоторого примера фича неприменима, не следует, что фича неприменима в общем случае; тем более, что есть прмеры, когда она применима. Это очевидно. Если нет, просто не надо вступать со мной в дискуссию; я исхожу из того, что собеседник умеет в кванторы и силлогизмы.
Это не повод замусоривать язык фичами, которые с высокой вероятностью в том же энтерпрайзе будут использоваться таким образом, что понизят читаемость и поддерживаемость кода.
Здравствуйте, Tesh, Вы писали:
IT>>Паскалевский опыт использования локальных функций у меня достаточно нейтральный. Т.е. ни плохо, ни хорошо. Нормальная удобная фича. Зато Немерловый опыт самый положительный. T>Если так нравятся языки с упором на функциональное программирование, то почему бы не использовать их?
На то есть много причин:
Во-первых, традиционные функциональные языки со своим синтаксисом — это зелёные человечки с другой планеты. Вроде бы как бы и всё логично, но совершенно анти-человечно.
Во-вторых, функциональная парадигма органично дополняет все остальные парадигмы включая ООП. Её место внутри метода, а снаружи весьма неплохо рулят другие. И хотелось бы воспользоваться всей мощью всех парадигм в одном языке. Великолепный пример такого гибридного языка — Немерле.
В-третьих, современный язык — это не только парадигма, синтаксис, библиотеки и фреймворки, но ещё и рабочее окружение ака Visual Studio или IntelliJ IDEA с полноценной поддержкой всего, а не только кривой подсветки синтаксиса.
На сегодняшний день, несмотря на многочисленные попытки, ни одного функционального языка, удовлетворяющего всем этим требованиями нет.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Tesh, Вы писали:
T>Немерле, как я понял, больше функциональный, чем объекто-ориентированный, поэтому больший уклон в функциональщину там может смотреться вполне логичным.
Немерле как язык — это качественный гибрид, в котором ФП, ООП и МП органично дополняют друг друга.
T>C# же скорее наоборот.
C# эволюционирует в правильном направлении. Единственная проблема в том, что он уже имеет некоторые косяки, которые могут припятствовать внедрению в него новых парадигм. Это может привести к некоторой перегрузке языака, которую при проектировании с нуля можно было бы избежать.
T>Как разультат, легко может случиться так, что в проекте, который передадут на развитие, будет человек со своим видением использования тех же локальных функций, что приведет к огромному количеству нечитаемого тяжело поддерживаемого кода.
Эта проблема существует даже на программируемом калькуляторе. У каждого своё видение и способности к производству нечитабельного кода.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>Эта проблема существует даже на программируемом калькуляторе. У каждого своё видение и способности к производству нечитабельного кода.
Вопрос в том насколько язык ограничивает, либо способствует написанию такого кода. Иначе можно возвести в абсолют: наводнить язык всевозможным количеством фич, защищая это тем, что спагетти код и без них может быть написан.
Как результат, время вхождения в проект только увеличится.
Здравствуйте, Tesh, Вы писали:
F>> Кроме того, как писать код — личное дело каждого. T>Я бы так не сказал, есть команда и внутри команды стиль написания и оформления кода должен быть предельно единым.
Тем не менее у каждой команды — свой отличимый от других стиль. Консистентность стиля внутри проекта/команды — это уже другой вопрос организации внутри команды.
F>>Никакого единого стиля не было и не будет, чисто по социальным причинам, и на практике C# код только лишь похож издалека по стилю. Что по факту — вообще никак не мешает — кода или слишком много и за 5 минут его невозможно понять, или код просто хреново написан (на любом языке), или — не хватает знаний, что бы понять код. T>Если говорить о разных проектах, то чем более строг язык, тем меньше вариантов реализации похожих вещей, а значит тем легче вхождение в новый проект. T>Если код за 5 минут невозможно понять, то в общем случае он не должен пройти ревью и должен быть переписан.
Задачи не ограничиваются только лишь написанием кода, но и багофиксинга. В период идентификации проблем, нужно "лазить" не только по коду своего проекта, но возможно и коллег, а иногда даже и по коду используемых библиотек или нижележащей платформы. Так вот во всём, с чем, вы лично не сталкивались — за 5 минут, как правило, мало что можно выяснить, и в общем случае — их код не может быть переписан (даже если вдруг и нужен фикс). А быстрота вхождения будет зависеть не сколько от языка и его фич, а сколько от знакомства с проблемой которую код решает. В этом плане, вдобавок, локальные функции будут сильно выигрывать, т.к. код и локализован и именован.
F>> Не преувеличивайте их значимость. Это всё же простая функция. В яваскрипте таких тонны и этого вообще никого не парит. Не парят они даже тех, кто не понимает что такое замыкание. F>> А вот ещё у человека может быть ещё и видение всей архитектуры иное (оправданное или нет), или его просто будет раздражать не им написанный код. И как результат — вне зависимости от наличия локальных функций, его передадут ещё раз. T>Сам факт возможности объявления полноценного метода внутри другого метода у меня не вызывает положительных эмоций.
И тем не менее — есть очень положительная практика среди других популярных языков.
Здравствуйте, Tesh, Вы писали:
T>Вопрос в том насколько язык ограничивает, либо способствует написанию такого кода. Иначе можно возвести в абсолют: наводнить язык всевозможным количеством фич, защищая это тем, что спагетти код и без них может быть написан. T>Как результат, время вхождения в проект только увеличится.
Я уже сказал, что время вхождения — зависит от опыта вообще и знакомства с предметом. Ну базовое знание языка как бы подразумевается и так.
А "спагетти" и так можно написать на любом языке. На C# и Java — например, делегаты, лямбды есть в языке. В C++ — их нет (новые C++ пока оставляем в стороне, это просто пример, а не C# vs C++). При этом в C++ делегаты разумеется используются в полный рост, тем или иным способом — в итоге код получается где-то более шумным, а между библиотеками — они разные (несовместимые). Ну и где же тут преимущество в ограничении нас в использовании фич?
Наоборот — чем больше возможностей есть — тем лучше (ещё круче, когда есть легальный способ расширения комплиятора). Тогда программисты имеют шанс воспользоваться стандартными средствами для решения своих задач.
Всё таки в контексте локальных функций — это вообще смешно звучит. Я вообще не встречал людей, у которых бы были с ними проблемы в JavaScript. А на фоне всех фич C# — эта (в плане использования / понятности) — вообще выглядит мелочью.
Здравствуйте, Qbit86, Вы писали:
Q>Критерий всё-таки другой. Дублирование — это такая копипаста, которая приводит к аномалиям редактирования. Если вместо вынесения общего функционала (в том числе пятистрочного) я его копипащу, то при редактировании мне нужно поправить все места пасты, это чреватое ошибками дублирование.
Копипаста это всегда дублирование. То что ты описываешь как "копипста без дублирования" — для этого больше подходит понятие "истина"/"truth" (как в SPOT — Single Point of Truth), то есть имеются несколько разных истин, которые временно имеют одинаковое значение.
Участки кода с разными контрактами могут иметь временно одинаковую реализацию, тем не менее это всё равно дублирование и копипаста. Вынесение такой общей реализации в отдельную функцию требует чёткого определения её контракта — тогда при изменениях реализации будет видно когда контракт меняется, а когда нет. А вот уже при изменении контракта нужно проверять всех пользователей, либо создавать отдельную функцию с новым контратком.
Необходимость формулировки и соблюдения контракта это тоже компромисс — и тут надо смотреть по ситуации что более целесообразно.
Здравствуйте, Tesh, Вы писали:
T>Правильно я понимаю, что мы каждый метод можем представить в виде команды, которая будет что-то делать, подписываться на события, как-то их обрабатывать, а затем отписываться (по необходимости)? И в итоге у нас будут небольшие легко читаемые классы-команды?
Небольшие классы-команды никогда не будут более легко читаемы, чем лямбды или локальные функции, если им надо захватывать замыкаемый контекст в конструкторе, или наоборот вытаскивать модифицируемые поля/свойства наружу, чтобы внешний скоуп их использовал вместо локальных переменных (как это происходит в коде, генерируемом компилятором из лямбд).
Здравствуйте, Sinix, Вы писали:
S>Ну низзя в общем случае для метода однозначно сказать: применима к нему tail call optimisation или нет.
Можно устранять все хвостовые вызовы. Включая виртуальные и замыкания.
Нужно только чтобы соглашения о вызове требовали очистку стека вызываемой функцией, а не вызывающей как сдуру сделали в С и по инерции тащат куда попало.
И называть оптимизацией tail call elimination нельзя. Ибо если код рассчитан на TCE, а его нет, то код упадёт.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, IT, Вы писали:
IT>А что ты понимаешь под локальностью я не совсем понял. Пояснишь?
Локальность: область видимости переменной (в том числе вложенной функции) должна быть минимальной; объявление должно быть подтянуто как можно ближе к использованию; по возможности в максимально вложенный блок (scope). Вплоть до того, что можно создать искусственный блок, намеренно ограничивающий область видимости временной переменной. В некоторых языках есть естественное органичение области видимости посредством let и where
Если в коде легко выцепить, в какой области переменная видна, и как долго «живёт», то это упрощает понимание и сопровождение кода. Снижает риски при рефакторинге.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Это изменение данных inplace, изменение состояния, последовательности действий. Типичный пример ИП — это quicksort (настоящий, тот который inplace).
В моем коде это довольно редкое явление. Большая часть функций получается чистыми.
Лично я предпочитаю ООП для дизайна системы в целом, плюс ФП для реализации. Ну, тоже без абсолютизма. Там где удобен ИП или он дает большую производительность, использую его.
EP>Что значит "в математике"? Императивное программирование это вполне себе математика.
Математика вообще не подразумевает изменения переменных. Так что ты совсем не верно понимаешь математику.
EP>А главное зачем? То есть мы решили — "внутри функций будем использовать ИП" — и для этого выбираем изначально чужеродный этой концепции язык и начинаем наворачивать монадический код, вместо того чтобы взять/сделать язык со встроенным, удобным и быстрым ;
А, какой язык ты называешь чужеродным ИП?
EP>Интерфейс в смысле API. Функциональный интерфейс — это когда "снаружи" у нас функции, причём чистые настолько, насколько это целесообразно.
Какой-то мало реалистичный подход. Первая коллекция разрушит этот интерфейс. Вот ФП внутри методов — это реалистично и используется на практике повсюду. Даже в C#.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Sinix, Вы писали: S>Только как делегат
Удачи в объяснении девелоперам причин, почему локальную функцию можно передать в Lync to Objects и нельзя в Lync to Sql.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Yoriсk, Вы писали: Y>Ничё, как-то "девелоперы" живут с этим.
Нет, сейчас не точно так же. Локальная функция будет отпадать на этапе компиляции, и её замена на именованную лямбду будет выглядеть примерно таким же шаманством, как обрамление имени аргумента скобками в джаваскрипте (aka "why 42..toFixed(2) == (42).toFixed(2)").
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Tesh, Вы писали:
T>Немерле, как я понял, больше функциональный, чем объекто-ориентированный, поэтому больший уклон в функциональщину там может смотреться вполне логичным. T>C# же скорее наоборот.
Я понимаю, если бы уклон предполагал отказ от чего-то. Но Nemerle не менее ОО, чем C#. А в функциональном плане C# его быстро догоняет и это очень здорово.
Здравствуйте, IT, Вы писали:
IT> К тому же приватные хелперы подталкивают неокрепшие умы к их повторному использованию (из разных методов). Тому повторному использованию, которое не только добро, но ещё и абсолютное, незамутнённое ни единой каплей добра, зло.
а можно пример такого зла?
p.s. в лямбдах очень не хватает yield return, рекурсию в лямбдах ни разу не хотел. ну а так в целом всегда хотелось, конечно, чтобы класс не "засорять".
Здравствуйте, hardcase, Вы писали:
H>Зачем?
Потому что могут
Если серьзно, то начиная с шестого шарпа очевидно поменялся подход к отбору фич.
Классика "дизайним очередной big thing и вылизываем всё, что с ним связано" никуда не делась.
Но помимо неё в язык по мере возможности добавляется мелочёвка, которая почти не влияет на public API и которая не особенно нужна в энтерпрайзе (серьёзно, если в коде такой баррдак, что даже приватные методы использовать небезопасно, ну сделай ты рефакторинг!).
Зато эта мелочёвка будет полезна для всяких скриптов/однодневных поделок и прочего write-only кода.
Это ни хорошо, ни плохо, просто последствия попытки пролезть в мобильную нищу и в нишу инди-сайтов.
Здравствуйте, 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 и тд и тп.
Здравствуйте, AngeL B., Вы писали:
AB>Для того чтобы опыт использования локальных функций был однозначно положительным, крайне желательно, чтобы язык поддерживал оптимизацию хвостовых рекурсий. Иначе опыт, приобретенный в Немерле/Скале/ФШарпе, может сыграть плохую службу. Вроде локальные функции есть, но нормальную рекурсию на них не напишешь.
В Немерле с этим всё в порядке.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, tyomchick, Вы писали:
T>Здравствуйте, Sinix, Вы писали:
T>По-моему полезная вещь. T>После Delphi очень не хватало.
В Дельфи был бледный аналог. Реальный прототип — немрел и другие наследники ML-я. Там это реально удобнейшая штука.
T>Засорять тонной private хелперов напрягает, а лямбды выглядят слишком кучеряво.
+1
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Добавлю от себя, что это еще и отличный способ документировать код. Периодически выношу в локальные функции код который вызвается ровно один раз. Функция инлайнится и не создает проблем с производительность, зато появляется именованный кусок логики, который можно свернуть фолдингом.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, IT, Вы писали:
IT>А мне очень не хватает локальной рекурсии, а вот локальный yield return ни разу не хотел, хотя использую его регулярно.
Локльный yield тоже приколен, но не для локальных функций, а для инлайн-итераторов (генераторов).
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, xy012111, Вы писали:
X>Что ещё весьма полезно — это возможность иметь локальные дженерик-функции
По моему опыту они нужны очень редко. Немерл их поддерживает, но мы их почему-то почти не используем. Видимо потому, что все алгоритмы локальны и им ненужны дженирики.
Если понадобились дженерики, то с огромной вероятностью функцию имеет смысл вынести в бибилотечный класс.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Sinix, Вы писали:
S>Но помимо неё в язык по мере возможности добавляется мелочёвка, которая почти не влияет на public API и которая не особенно нужна в энтерпрайзе (серьёзно, если в коде такой баррдак, что даже приватные методы использовать небезопасно, ну сделай ты рефакторинг!).
Звучит так, что этот ваш энтерпрайз — это что-то очень плохое рассчитанное на участников специальных олимпиад.
S>Зато эта мелочёвка будет полезна для всяких скриптов/однодневных поделок и прочего write-only кода. S>Это ни хорошо, ни плохо, просто последствия попытки пролезть в мобильную нищу и в нишу инди-сайтов.
Практика показала, что локальные функции помогает очистить и структурировать код. Так что мимо.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>На самом деле реализовать дженерик-локальнфе функции сложнее. Там есть подводные грабли.
О! А можно подробности?
S>>UPD Как вариант, пригодится для скриптов и compiler as a service. Весь код запихиваем в одно тело метода и пусть оно себе компилируется, если сможет. VD>Зачем?
Ну а зачем в типовом скрипте что-то кроме локальных функций? Плюс, если вводить методы — клиентам надо будет прописывать сигнатуру основного метода.
Здравствуйте, Sinix, Вы писали:
VD>>Практика показала, что локальные функции помогает очистить и структурировать код. Так что мимо. S>Без обид, но нет. Скорее, получается копипаста и дублирование кода из-за того, что никто не знает, что подобный код уже написан. Разумеется, это справедливо только для проектов, в которых один человек в принципе не сможет охватить всю codebase.
В первых, о приватных методах тоже легко забыть и переопределить аналогичную логику в другом месте. Если же ты говоришь о тупой копипасте, когда локальную функцию копируют в другой метод, то тут просто разработчик не понимает, что общую логику нужно выносить в отдельный метод. А в случае с плохим разработчиком тебе приватные методы тоже не очень помогут (логика может понадобится в другом классе, например).
Зато локальные функции дают +10 к читаемости и поддерживаемости кода. Сравни:
public string DoStuff(string text, string text2)
{
if (ExtractInfo(text) == "HELLO") return ExtractInfo(text2);
...
}
public void A()
{
...
}
public void B()
{
...
}
private string ExtractInfo(string text)
{
...
}
и
public string DoStuff(string text, string text2)
{
var extractInfo = (s) => ....;
...
if (extractInfo(text) == "HELLO") return extractInfo(text2);
...
}
В первом случае тебе нужно перейти на определение приватного метода, потом вернуться обратно. Если функция сложнее, чем Substring, то делать это придётся несколько раз, чтобы удостовериться в корректности кода.
Локальная функция всегда рядом с местом использования, ты одновременно видишь как определение, так и применение.
Это одно из преимуществ. Другие тебе перечислили выше.
Имхо, любой, кто некоторое время писал на языке с нормальной поддержкой локальных функций, впоследствии будет по ним скучать в том же C#.
Здравствуйте, Sinix, Вы писали:
S>... а не надуманный пример. Смысл растаскивать методы-локальные хелперы от метода, в котором они используются? S>И в таком виде первый вариант выигрывает без вариантов. Т.е. первый же отрыв от сфероконины и переход к реальному коду — уже упс.
Спорить не буду, сам всё увидишь, если всё-таки добавят.
S>И да, для студии с peek definition вся эта возня с "в каком порядке расположено" особого смысла не имеет. Щёлкнул alt-f12 — увидел рядом реализацию, при необходимости поправил. Попробуйте, это одна из самых недооценённых фишек vs2013.
Это да, никак не могу заставить себя ей пользоваться, несмотря на явную полезность. Но чтобы подправить код, в любом случае придётся перемещаться.
Здравствуйте, Sinix, Вы писали:
S>Это постараться надо. Методы обычно называются логично, поиск студии или решарпера (который go to symbol, показывает подходящие методы в реалтайме) находит кандидатов на раз-два. Не, поддержка приватных методов тоже добавится со временем, но во-первых там больше шансов наткнуться на непонятное имя, во-вторых, смысл в разделении теряется окончательно. S>а не надуманный пример. Смысл растаскивать методы-локальные хелперы от метода, в котором они используются? S>И в таком виде первый вариант выигрывает без вариантов. Т.е. первый же отрыв от сфероконины и переход к реальному коду — уже упс.
Локальные функции — это вопрос скоупа. Просто найди все обычные рекомендации из C++/C# о том, что надо объявляеть локальные переменные ближе к их использованию, и зачем ограничивать их области видимости. А потом примени эти рекомендации к локальным функциям. Выносить локальные функции в скоуп класса так же неприятно, как выносить локальные переменные алгоритма в поля класса.
S>И да, для студии с peek definition вся эта возня с "в каком порядке расположено" особого смысла не имеет. Щёлкнул alt-f12 — увидел рядом реализацию, при необходимости поправил. Попробуйте, это одна из самых недооценённых фишек vs2013.
Здравствуйте, Sinix, Вы писали:
S>Roslyn team завела обсуждение на local functions (объявление функции внутри функции).
Это какие замыкания в C# по счёту? Третьи?
S>Официальные комментарии такие: S>1. Упрощает код — не всегда очевидно, что функция — просто хелпер для одного конкретного метода и вызывать её не по делу не надо.
Для этого достаточно лямбды.
S>2. лямбды не умеют в yield return. Т.е. самый простой с валидацией аргументов превращается в
В чём проблема добавить?
S>3. Перфоманс. Создание лямбды на каждый вызов, и, главное, отсутствие инлайнинга — не лучший способ добавить хелпер-код.
Казалось бы — почему бы не оптимизировать лямбды?
S>4. Чтоб было совсем весело, в этого ужеежа запихнули фичи и от лямбд и от "обычных" методов: S>* local functions могут вести себя как лямбды
В каком смысле?
S>* вывод типа для результата (если нет рекурсии)
Лямбды тоже могли бы это уметь.
S>* рекурсия
Пожалуй единственный аргумент за.
S>* захват переменных (aka lambda closures)
Это и так умеет лямбда.
S>* ref/out — параметры, named args, default arg values etc.
Здравствуйте, AlexRK, Вы писали:
EP>>Это какие замыкания в C# по счёту? Третьи? EP>>Это и так умеет лямбда. ARK>Я бы вместо добавления локальных функций урезал лямбды — запретил им быть замыканиями
То что лямбды являются замыканиями — для меня одна из главных фич, так как это позволяет сократить O(N) количество кода захвата до O(1).
ARK>и содержать больше одного выражения.
В Python примерно так и есть — там у лямбд только один statement (правда они в отличие от твоего предложения всё же замыкания), а для больших замыканий есть локальные функции.
ARK>С целью слегка ограничить полет фантазии особо одаренных.
От этого помогает code review, так как такая фантазия может улететь куда угодно.
ARK>Правда, уверен, в текущем обсуждении меня мало кто поддержит.
А мне нравятся мощные, быстрые, полиморфные (типа "generic") лямбды — как в C++
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>То что лямбды являются замыканиями — для меня одна из главных фич, так как это позволяет сократить O(N) количество кода захвата до O(1).
Замечание верно, но меня пугают лямбды, у которых используется куча захваченных переменных.
Лично я смотрю на лямбды, как на удобные сокращения для очень маленьких функций, например в LINQ: list.OrderBy(x => x.Name);
Здравствуйте, 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.
Меня в своё время это адски и неимоверно раздражало: любая, даже неявная рекурсия — потенциальная точка отказа для всего процесса. Блин.
Здравствуйте, Qbit86, Вы писали:
Q>Из того, для некоторого примера фича неприменима, не следует, что фича неприменима в общем случае; тем более, что есть прмеры, когда она применима. Это очевидно. Если нет, просто не надо вступать со мной в дискуссию; я исхожу из того, что собеседник умеет в кванторы и силлогизмы.
Ок С возражениями согласен, свою точку зрения я уже высказал, так что закругляемся
S>>Одна проблема: если это делать — то делать для всех функций, не только для локальных. Q>Что делать для всех функций? При чём тут Тьюринг?
Ну низзя в общем случае для метода однозначно сказать: применима к нему tail call optimisation или нет.
Можно бросать предупреждения или молча не применять оптимизацию в ряде случаев, но тогда от исходной проблемы мы никуда не денемся.
Здравствуйте, IT, Вы писали:
IT>Паскалевский опыт использования локальных функций у меня достаточно нейтральный. Т.е. ни плохо, ни хорошо. Нормальная удобная фича. Зато Немерловый опыт самый положительный.
Если так нравятся языки с упором на функциональное программирование, то почему бы не использовать их?
Здравствуйте, Sinix, Вы писали:
S>Ну низзя в общем случае для метода однозначно сказать: применима к нему tail call optimisation или нет. S>>>Одна проблема: если это делать — то делать для всех функций, не только для локальных.
Зачем непременно для всех? Если вдруг сделают для какого-то подмножества хвостовых вызовов, скажем, рекурсивных хвостовых вызовов (для них проще, чем в общем случае), то уже будет хорошо. Но к «локальным функциям» этот вопрос перпендикулярен, они удобны незавсимо от наличия оптимизации хвостовых вызовов, рекурсивных или нет.
Здравствуйте, Qbit86, Вы писали:
Q>На мой взгляд, именованные замыкания с высокой вероятностью повысят читаемость и сопровождаемость кода.
Короткие функции типа лямбд, используемых в linq, не требуют именования, а более крупные, объявленные внутри другого метода, только увличат объем этого метода и ухудшат читаемость.
Здравствуйте, Tesh, Вы писали:
T>Короткие функции типа лямбд, используемых в linq не требуют именования
А если они вызываются рекурсивно или просто неоднократно? Скажем, используются при декларации отписки от события:
{
local void HandleSomeEvent(object sender, EventArgs e)
{
// Captures some variables from outer scope.
}
subject.SomeEvent += HandleSomeEvent;
_subscriptionDisposables.Add(() => { subject.SomeEvent -= HandleSomeEvent; });
...
}
T>а более крупные, объявленные внутри другого метода, только увличат объем этого метода и ухудшат читаемость.
Если выносить вложенные функции в методы того же класса, то они не смогут быть замыканиями; и не смогут гарантировать, что другие методы не начнут вызывать вынесенный, затрудняя его модификацию.
Здравствуйте, Qbit86, Вы писали:
Q>Если выносить вложенные функции в методы того же класса, то они не смогут быть замыканиями; и не смогут гарантировать, что другие методы не начнут вызывать вынесенный, затрудняя его модификацию.
Я бы вообще не использовал события из-за опасности утечки памяти (если забыли отписаться), но если и использовать, то зачем объединять код, обрабатывающий события с кодом, выполняющим подписку?
По поводу вызова обработчкика событий другими методами класса — если класс компактный и состоит из пары-тройки методов, то это не является проблемой.
Здравствуйте, Tesh, Вы писали:
T>Немерле, как я понял, больше функциональный, чем объекто-ориентированный, поэтому больший уклон в функциональщину там может смотреться вполне логичным. T>C# же скорее наоборот.
Скорее наоборот. На уровне деклараций классов / методов — он будет очень похож на C# (что логично). Внутри методов — отличия есть, но в сторону бОльшего удобства. Никто всю функциональщину использовать не заставляет. С Немерле, как с языком, очень легко начать. А вот с F# (для меня лично) — лучше удавиться сразу, потому что — становится совершенно неочевидно, где же там шарп.
T>Что мне не нравится в подобных попытках перенести все возможные фичи в один язык, так это тем, что это не способствует единообразию кода между проектами и получается очередной C++ с многообразием способов прострелить себе ногу.
Всего фич на самом деле не так и много, и в том или ином виде присутствуют или реализуются в других языках. В C++ скорее мешает многословность и мешанина, даже (с моей точки зрения) в очень хорошо структурированных проектах. Кроме того, как писать код — личное дело каждого. Никакого единого стиля не было и не будет, чисто по социальным причинам, и на практике C# код только лишь похож издалека по стилю. Что по факту — вообще никак не мешает — кода или слишком много и за 5 минут его невозможно понять, или код просто хреново написан (на любом языке), или — не хватает знаний, что бы понять код.
T>Как разультат, легко может случиться так, что в проекте, который передадут на развитие, будет человек со своим видением использования тех же локальных функций, что приведет к огромному количеству нечитаемого тяжело поддерживаемого кода.
Не преувеличивайте их значимость. Это всё же простая функция. В яваскрипте таких тонны и этого вообще никого не парит. Не парят они даже тех, кто не понимает что такое замыкание.
А вот ещё у человека может быть ещё и видение всей архитектуры иное (оправданное или нет), или его просто будет раздражать не им написанный код. И как результат — вне зависимости от наличия локальных функций, его передадут ещё раз.
Здравствуйте, IT, Вы писали:
IT>А так идея не понятна? К сожалению, куска из реального проекта не имеется, потому как такие авгиевы конюшни вычищаются в первую очередь.
Так не понятно, можно ли этот класс декомпозировать или нет.
Здравствуйте, Qbit86, Вы писали:
Q>А зачем их разносить, если подписка осуществляется только в одном месте и код обработчика нежелательно вытаскивать наружу?
Почему нежелательно? Вообще зависит от того на что и где подписываетесь. Если сам объект, генерирующий события, живет только в пределах одного метода, и логика обработки события предельно проста, то можно и обычные лямбды использовать.
Если же объект живет в пределах класса, либо логика обработки события более сложная, то по-моему такой обработчик нужно выносить в отдельный метод.
Если бы был какой-то реальный пример, то можно было бы более предметно обсуждать.
Здравствуйте, fddima, Вы писали:
T>>Что мне не нравится в подобных попытках перенести все возможные фичи в один язык, так это тем, что это не способствует единообразию кода между проектами и получается очередной C++ с многообразием способов прострелить себе ногу. F> Кроме того, как писать код — личное дело каждого.
Я бы так не сказал, есть команда и внутри команды стиль написания и оформления кода должен быть предельно единым.
F>Никакого единого стиля не было и не будет, чисто по социальным причинам, и на практике C# код только лишь похож издалека по стилю. Что по факту — вообще никак не мешает — кода или слишком много и за 5 минут его невозможно понять, или код просто хреново написан (на любом языке), или — не хватает знаний, что бы понять код.
Если говорить о разных проектах, то чем более строг язык, тем меньше вариантов реализации похожих вещей, а значит тем легче вхождение в новый проект.
Если код за 5 минут невозможно понять, то в общем случае он не должен пройти ревью и должен быть переписан.
F> Не преувеличивайте их значимость. Это всё же простая функция. В яваскрипте таких тонны и этого вообще никого не парит. Не парят они даже тех, кто не понимает что такое замыкание. F> А вот ещё у человека может быть ещё и видение всей архитектуры иное (оправданное или нет), или его просто будет раздражать не им написанный код. И как результат — вне зависимости от наличия локальных функций, его передадут ещё раз.
Сам факт возможности объявления полноценного метода внутри другого метода у меня не вызывает положительных эмоций.
Здравствуйте, Tesh, Вы писали:
IT>>А так идея не понятна? К сожалению, куска из реального проекта не имеется, потому как такие авгиевы конюшни вычищаются в первую очередь. T>Так не понятно, можно ли этот класс декомпозировать или нет.
А это тут каким боком?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Tesh, Вы писали:
T>Вопрос в том насколько язык ограничивает, либо способствует написанию такого кода.
Ни у одного вменяемого языка программирования нет блокирующих функций ограничения деятельности программиста. И это правильно. Есть функции подстраховки, как, например, строгая типизация. Но даже в этом случае оставляется возможность её обхода.
T>Иначе можно возвести в абсолют: наводнить язык всевозможным количеством фич, защищая это тем, что спагетти код и без них может быть написан.
Не надо путать ковырятельство в отходах производства с освоением новых технологий. Захламить язык можно только хламом. Новые фичи, органично вписанные в язык, сделают его только мощнее. И, кстати, этих фич осталось не так уж много.
T>Как результат, время вхождения в проект только увеличится.
Это старая песня закалённого в интригах менеджера про пучок индусов вместо одного вменяемого разработчика. Тут есть только одна проблема — функция Sum для IQ не работает. Работает только функция Max. Тренеруйте своих разработчиков, гоните в шею бездарей и всё у вас получится. Посредственности могут выдавать только посредственный результат.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Qbit86, Вы писали:
Q>Быструю сортировку можно и без рекурсивных вызовов организовать, эмулировав стек, но так обычно не делают в стандартных библиотеках. То есть почему-то не спешат менять потенциальный StackOverflowEcxeption на потениальный OutOfMemoryException. Добавляют при необходимоти guard глубины рекурсии, и живут себе, поди, без штрафов к премиям.
Справедливости ради, в нормальной quicksort глубина стэка гарантированно ограничена O(ln(N)), в worst case.
Там внутри две рекурсии — по левой стороне массива и по правой. Для той у которой размер массива больше — делают ручной tail call. То есть рекурсия происходит всегда в меньшую сторону, причём это может быть как левая, так и правая сторона.
Размер меньшей части гарантированно меньше половины размера исходного массива, то есть рекурсия как минимум делится пополам, что гарантирует логарифмическую глубину.
Ограничение глубины рекурсии (причём не "реальной" глубины, а чисто алгоритмической, то есть без учёта tail call) — делается с целью перехода на алгоритм сортировки с гарантированной сложностью O(N*ln(N)) — чтобы получить общую сложность алгоритма O(N*ln(N)) в worst case.
Так, например, introsort сначала использует quicksort (потому что очень быстрая и cache-friendly), потом если глубина превышает заданный порог то делается heapsort (потому что гарантированно O(N*ln(N))), а потом ещё в некоторых вариантах когда размер всех под-масивов мал — выполняется финальное расчёсывание через insertion sort — так например во многих реализациях STL.
P.S. А вообще, на x64 проблема с переполнением стэка от рекурсии менее актуальна — достаточно зарезервировать побольше адресов под стэк.
IT>Это старая песня закалённого в интригах менеджера про пучок индусов вместо одного вменяемого разработчика. Тут есть только одна проблема — функция Sum для IQ не работает. Работает только функция Max.
Скорее, Max-Stdev. Ибо ещё и разгребать за остальными
Здравствуйте, Sinix, Вы писали:
EP>>"итераторы/await" — это всё же не замыкания. Под замыканием я понимаю прежде все захват переменных из внешнего scope. S>Ну да, переменные(параметры) в итераторах и ко сохраняются между вызовами MoveNext()
Из своего scope, а не из чужого. yield/await в C# это во сути rewrite отдельного метода целиком в класс-автомат, но захвата из внешнего scope там нет.
EP>>Вообще, под первыми замыканиями я имел в виду: S>А, ну так анонимные делегаты и лямбды — эт одно и то же по большому счёту. Можно их по отдельности считать, тогда больше будет
Дополнительный синтаксис загромождающий язык.
EP>>А почему для лямбд это трудно, а для local functions нет? В чём разница? Лямбды завязаны на какое-то ABI jit'а? S>Неа, потому что лямбды — это делегаты, их значение может меняться в рантайме.
В каком смысле? Что это даёт по сравнению с локальными функциями?
S>Локальные функции ничем не отличаются от вызова приватного метода, для них отдельных приседаний делать не надо.
Насколько я понял по форме отличий мало, отличаются реализации. Что мешает для старой формы сделать быструю реализацию?
S>Даже для замыканий емнип всё ок, невиртуальные вызовы могут быть заинлайнены. Надо проверять конечно, могу наврать.
Тут просто часто говорят что лямбды в C# медленные, постоянные аллокации и т.п. — по сути стоит выбор между удобством и скоростью — я вот и не понимаю почему их не оптимизировать в компиляторе, чтобы не было такой дилеммы там где её быть не должно.
EP>>Если исправят скорость лямбд, то от этого выиграет уже существующий код. S>Там править нечего практически, стоимость уже сопоставима с вызовом без инлайнинга емнип. Где-то у меня был пример, надо будет найти.
Здравствуйте, IT, Вы писали:
IT>Во-вторых, функциональная парадигма органично дополняет все остальные парадигмы включая ООП. Её место внутри метода, а снаружи весьма неплохо рулят другие.
Внутри функций рулит императивный код. Отдельные элементы исторически характерные функциональным языкам типа ФВП, замыканий, АТД, PM и т.п. — действительно удобны, но они никак не определяют функциональную парадигму.
ФП это прежде всего чистота (которая кстати скорее про внешнюю сторону функций, а не внутреннюю), а не какие-нибудь замыкания (их наоборот в ФП стараются не использовать — point-free style).
Здравствуйте, Tesh, Вы писали:
T>Вообще зависит от того на что и где подписываетесь. Если сам объект, генерирующий события, живет только в пределах одного метода, и логика обработки события предельно проста, то можно и обычные лямбды использовать. T>Если бы был какой-то реальный пример, то можно было бы более предметно обсуждать.
Я на работе регулярно сталкиваюсь с API, где асинхронность функций реализуется через глобальное событие. (Причём это в библиотеках стоимостью по полсотни баксов за убогую C#-обёртку над несколькими нативными и Java-библиотеками для мобильных сервисов.) По сравнению с такой моделью асинхронности, callback hell вовсе не кажется чем-то страшным и неприятным, поверьте.
Скажем, функция платежа в биллинг-системе, или постинг в социальную сеть, или что там ещё. Для функций типа StaticBunchOfFunctions.doSomething() есть публичный StaticBunchOfEvents.onSomethingEndedEvent (стиль именования сохранён, про сигнатуры EventHandler<T> никто из авторов не слышал). Ты можешь звать функцию в разных контекстах; в каком контексте ты получишь событие предсказать нельзя, как в обработчике различить источник вызова — та ещё задача. Кто угодно может звать глобальную функцию, кто угодно может подписываться, не отписываться, получать событие дважды, etc. Нужно административными методами запрещать пересекающиеся по времени вызовы, как-то протаскивать контекст, etc.
Поэтому вызов выглядит как-то так (псевдокод):
{
...
local void HandleSomethingCompleted(Some weirdParams)
{
StaticBunchOfEvents.onSomethingEndedEvent -= HandleSomethingCompleted;
// Perform some continuation capturing locals from outer scope.
}
StaticBunchOfEvents.onSomethingEndedEvent += HandleSomethingCompleted;
StaticBunchOfFunctions.doSomething();
}
T>такой обработчик нужно выносить в отдельный метод.
Тогда обработчик в общем случае будет не методом, а полноценным классом-замыканием, с захватом полей в конструкторе и прочим синтаксическим оверхедом.
Правильно я понимаю, что мы каждый метод можем представить в виде команды, которая будет что-то делать, подписываться на события, как-то их обрабатывать, а затем отписываться (по необходимости)? И в итоге у нас будут небольшие легко читаемые классы-команды?
Здравствуйте, Qbit86, Вы писали:
Q>Небольшие классы-команды никогда не будут более легко читаемы, чем лямбды или локальные функции, если им надо захватывать замыкаемый контекст в конструкторе, или наоборот вытаскивать модифицируемые поля/свойства наружу, чтобы внешний скоуп их использовал вместо локальных переменных (как это происходит в коде, генерируемом компилятором из лямбд).
По-моему, все как раз наоборот — они всегда будут более легко читаемы, чем огромные функции с лямбдами или вложенными функциями.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
IT>>Во-вторых, функциональная парадигма органично дополняет все остальные парадигмы включая ООП. Её место внутри метода, а снаружи весьма неплохо рулят другие. EP>Внутри функций рулит императивный код.
Особенно хорошо императив рулит говнокодом.
EP>Отдельные элементы исторически характерные функциональным языкам типа ФВП, замыканий, АТД, PM и т.п. — действительно удобны, но они никак не определяют функциональную парадигму.
Совершенно верно. Наследование, инкапсуляция и полиморфизм, особенно по отдельности, тоже никак не определяют парадигму ООП. Это просто набор удобных инструментов.
EP>ФП это прежде всего чистота (которая кстати скорее про внешнюю сторону функций, а не внутреннюю), а не какие-нибудь замыкания (их наоборот в ФП стараются не использовать — point-free style).
Внешняя чистота функции определяется прежде всего её внутренней реализацией, которой как раз и достигаются детерминированность и отсутствие побочных артефактов.
Если нам не помогут, то мы тоже никого не пощадим.
Со всем согласен.
Q>В общем все те твои рассуждение про изолированность частей кода, про инкапсуляцию и локальность.
Это всё не более чем терминологические заклинания. Изоляция и инкапсуляция — это вообще-то одно и тоже, суть — сокрытие деталей. А что ты понимаешь под локальностью я не совсем понял. Пояснишь?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
EP>>Отдельные элементы исторически характерные функциональным языкам типа ФВП, замыканий, АТД, PM и т.п. — действительно удобны, но они никак не определяют функциональную парадигму. IT>Совершенно верно. Наследование, инкапсуляция и полиморфизм, особенно по отдельности, тоже никак не определяют парадигму ООП. Это просто набор удобных инструментов.
Если не использовать ни объекты, ни наследование, ни инкапсуляцию, ни полиморфизм — то это не будет является ООП.
При этом существуют языки в которых нет ни замыканий/лямбд, ни АТД, ни PM — и при этом они являются функциональными, например Unlambda.
ФВП правда есть во всех которых знаю, да. Будет ли называться язык ФЯ если убрать ФВП —
EP>>ФП это прежде всего чистота (которая кстати скорее про внешнюю сторону функций, а не внутреннюю), а не какие-нибудь замыкания (их наоборот в ФП стараются не использовать — point-free style). IT>Внешняя чистота функции определяется прежде всего её внутренней реализацией, которой как раз и достигаются детерминированность и отсутствие побочных артефактов.
Да, но это работает и в другую сторону: внешняя чистота функции накладывает некоторые ограничения на внутреннюю реализацию, то есть частично её определяет. При этом у чистой снаружи функции могут быть насквозь императивные внутренности. Чистый интерфейс никак не означает purely functional внутренности.
Здравствуйте, IT, Вы писали:
EP>>Чистый интерфейс никак не означает purely functional внутренности. IT>И какой вывод? Я что-то потерял нить дискуссии.
Вообще мой поинт в следующем:
* В теле функций лучше всего себя показывает императивное программирование вкупе с некоторыми элементами характерными ФЯ — функции высшего порядка, лямбды/замыкания, и т.п. Добавление таких элементов не делает такой код функциональным — это всё то же ИП с явным мутированием и итерациями.
* Интерфейсы же лучше всего делать функциональными и как можно более чистыми.
* Функции имеют больший приоритет нежели методы объектов. Акцент нужно делать на "ООП это один из поставщиков типов для параметров функций и их реализаций", а не на "реализации методов объектов используют статические-методы-помощники" (на что в своё время сделала акцент Java).
То есть по сути — ФП снаружи, ИП с элементами ФЯ внутри, а ООП где-то сбоку.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EEP>Вообще мой поинт в следующем: EP>* В теле функций лучше всего себя показывает императивное программирование вкупе с некоторыми элементами характерными ФЯ — функции высшего порядка, лямбды/замыкания, и т.п. Добавление таких элементов не делает такой код функциональным — это всё тоже ИП с явным мутированием и итерациями.
Тут неясно тогда что такое императивный подход.
Вот в Немерле — нет стэйтментов в привычном их понимании, и они — все выражения. При этом хоть в ФЯ хоть в математике, тебе никто не мешает определить оператор ; который будет выполнять туже самую роль, что и в императивных языках. При этом, в C/C++ — есть оператор , который выполняет туже роль, что и ; — но для выражений. Дублирование на лицо. Кроме того любой стэйтмент (if/switch и т.п.) — можно представить в виде функции. А в том же Немерле — в добавок, к этому дают не уродливый синтаксис вроде IIF(x,y,z) — а то, к чему мы все привыкли.
Но, я боюсь, что я тебя не совсем правильно понял, и унесло меня не туда.
EP>* Интерфейсы же лучше всего делать функциональными и как можно более чистыми.
Интерфейс чего? Я не понял. И что значит функциональный интерфейс?
EP>* Функции имеют больший приоритет нежели методы объектов. Акцент нужно делать на "ООП это один из поставщиков типов для параметров функций и их реализаций", а не на "реализации методов объектов используют статические-методы-помощники" (на что в своё время сделала акцент Java). EP>То есть по сути — ФП снаружи, ИП с элементами ФЯ внутри, а ООП где-то сбоку.
Если тебе кажется что все парадигмы — это лишь названия и об одном и том же, — то с философской точки зрения — это так и есть. Собственно "функция" от метода — по большому счету в практическом смысле неотличима (реализации оставляем в стороне), но когда копаем глубже — разница всё сильнее и сильнее всплывает, и начинают немного меняться подходы. Хотя в целом — всё одно.
По крайней мере — я считаю — что всё одно.
Тем не менее — я считаю, что компонентная модель — это краеугольный камень развития софта вообще. Поэтому, мне подуше nemerle-like way — ООП снаружи, а остальное должно волновать только разработчика (ну с немерле это ещё и потому, что — хочешь быть полноценнов дотнете — просто будь полноценным). К сожалению стандартной компонентной модели не существует, и весь этот ниндзя-стайл кодинг в C++ остаётся возможным только внутри модулей (я не думаю что сэры действительно захотят использовать несовместимые недохаки навроде экспорта класса — а если сэры разрабатывают библиотеки — то клиенты в любом случае будут несчастны), при этом хидеры — всё ещё нужны. Поэтому .NET и Java — жутко рулят. Потому, что этим не только можно, но ещё и удобно пользоваться.
И вот на фоне этого — ФП, ООП или ИП — для меня лично — вообще сбоку.
PS: Я повторюсь — исходный посыл я наверное не понял, и унесло меня не туда.
Здравствуйте, AlexRK, Вы писали:
Q>>Небольшие классы-команды никогда не будут более легко читаемы, чем лямбды или локальные функции, если им надо захватывать замыкаемый контекст в конструкторе, или наоборот вытаскивать модифицируемые поля/свойства наружу, чтобы внешний скоуп их использовал вместо локальных переменных (как это происходит в коде, генерируемом компилятором из лямбд). ARK>По-моему, все как раз наоборот — они всегда будут более легко читаемы, чем огромные функции с лямбдами или вложенными функциями.
Забавно, но у вас — всё наоборот. Вы хоть с кем-нибудь согласны?
Здравствуйте, fddima, Вы писали:
EEP>>Вообще мой поинт в следующем: EP>>* В теле функций лучше всего себя показывает императивное программирование вкупе с некоторыми элементами характерными ФЯ — функции высшего порядка, лямбды/замыкания, и т.п. Добавление таких элементов не делает такой код функциональным — это всё тоже ИП с явным мутированием и итерациями. F> Тут неясно тогда что такое императивный подход.
Это изменение данных inplace, изменение состояния, последовательности действий. Типичный пример ИП — это quicksort (настоящий, тот который inplace).
F> Вот в Немерле — нет стэйтментов в привычном их понимании, и они — все выражения.
А это тут причём?
F>При этом хоть в ФЯ хоть в математике,
Что значит "в математике"? Императивное программирование это вполне себе математика.
F>тебе никто не мешает определить оператор ; который будет выполнять туже самую роль, что и в императивных языках.
Как? Через монады, bind'ы и трёхэтажные замыкания под капотом do-сахара? Как это отображается в современное насквозь императивное железо?
А главное зачем? То есть мы решили — "внутри функций будем использовать ИП" — и для этого выбираем изначально чужеродный этой концепции язык и начинаем наворачивать монадический код, вместо того чтобы взять/сделать язык со встроенным, удобным и быстрым ;
F>При этом, в C/C++ — есть оператор , который выполняет туже роль, что и ; — но для выражений. Дублирование на лицо.
Я не вижу причём тут expressions vs statements.
EP>>* Интерфейсы же лучше всего делать функциональными и как можно более чистыми. F> Интерфейс чего? Я не понял. И что значит функциональный интерфейс?
Интерфейс в смысле API. Функциональный интерфейс — это когда "снаружи" у нас функции, причём чистые настолько, насколько это целесообразно.
EP>>То есть по сути — ФП снаружи, ИП с элементами ФЯ внутри, а ООП где-то сбоку. F> Если тебе кажется что все парадигмы — это лишь названия и об одном и том же, — то с философской точки зрения — это так и есть.
В каком смысле?
F>К сожалению стандартной компонентной модели не существует, и весь этот ниндзя-стайл кодинг в C++ остаётся возможным только внутри модулей
Здравствуйте, fddima, Вы писали:
Q>>>Небольшие классы-команды никогда не будут более легко читаемы, чем лямбды или локальные функции, если им надо захватывать замыкаемый контекст в конструкторе, или наоборот вытаскивать модифицируемые поля/свойства наружу, чтобы внешний скоуп их использовал вместо локальных переменных (как это происходит в коде, генерируемом компилятором из лямбд). ARK>>По-моему, все как раз наоборот — они всегда будут более легко читаемы, чем огромные функции с лямбдами или вложенными функциями. F> Забавно, но у вас — всё наоборот. Вы хоть с кем-нибудь согласны?
Конечно согласен, например с пользователями Sinix и Tesh.
Хотя к чему этот вопрос был, не очень понятно. Считаете свою точку зрения единственно верной?
Здравствуйте, AlexRK, Вы писали:
ARK>Конечно согласен, например с пользователями Sinix и Tesh. ARK>Хотя к чему этот вопрос был, не очень понятно. Считаете свою точку зрения единственно верной?
Вовсе нет. Просто хотелось понять.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>>>* В теле функций лучше всего себя показывает императивное программирование вкупе с некоторыми элементами характерными ФЯ — функции высшего порядка, лямбды/замыкания, и т.п. Добавление таких элементов не делает такой код функциональным — это всё тоже ИП с явным мутированием и итерациями. F>> Тут неясно тогда что такое императивный подход. EP>Это изменение данных inplace, изменение состояния, последовательности действий. Типичный пример ИП — это quicksort (настоящий, тот который inplace).
Понятно. quicksort — это не сколько ИП, а сколько алгоритм сортировки in-place. При чём, он сам по себе отлично реализуется и в ФЯ.
Да, и, в моём коде, на C# — много методов, которые можно было бы назвать чистыми функциями. Но, чистые функции — сами по себе не самоцель.
EP>А это тут причём? F>>При этом хоть в ФЯ хоть в математике, EP>Что значит "в математике"? Императивное программирование это вполне себе математика. F>>тебе никто не мешает определить оператор ; который будет выполнять туже самую роль, что и в императивных языках. EP>Как? Через монады, bind'ы и трёхэтажные замыкания под капотом do-сахара? Как это отображается в современное насквозь императивное железо? EP>А главное зачем? То есть мы решили — "внутри функций будем использовать ИП" — и для этого выбираем изначально чужеродный этой концепции язык и начинаем наворачивать монадический код, вместо того чтобы взять/сделать язык со встроенным, удобным и быстрым ; F>>При этом, в C/C++ — есть оператор , который выполняет туже роль, что и ; — но для выражений. Дублирование на лицо. EP>Я не вижу причём тут expressions vs statements.
Сомневаюсь. Итерирования, как такового в математике пожалуй нет.
Зачем монады и bind-ы я не понял. Что мешает иметь оператор ; в ФЯ, ну кроме научных комплексов? Кроме того, гибридные ФЯ именно это и позволяют.
Expressions vs statements — к тому, что statements заставляют код выглядить "императивным", даже где это не нужно. Весь привычный flow-control, кроме goto — отлично представляется ввиде выражений. Более того, опять же — практика Scala и Nemerle — показывает, что это очень удобно.
EP>>>* Интерфейсы же лучше всего делать функциональными и как можно более чистыми. F>> Интерфейс чего? Я не понял. И что значит функциональный интерфейс? EP>Интерфейс в смысле API. Функциональный интерфейс — это когда "снаружи" у нас функции, причём чистые настолько, насколько это целесообразно.
Это уже закончилось прокачкой магических хэндлов в параметры любого API, от API OS до API библиотек. При этом большинство API — именно, что объектно-ориентировано. Но, если API OS — в общем случае обусловлено историческими реалиями — то современный софт явно требует другого.
EP>>>То есть по сути — ФП снаружи, ИП с элементами ФЯ внутри, а ООП где-то сбоку. F>> Если тебе кажется что все парадигмы — это лишь названия и об одном и том же, — то с философской точки зрения — это так и есть. EP>В каком смысле?
В том, что все парадигмы программирования — разные инструменты для достижения одной и той же цели. В итоге — это выполняется на конкретном железе с ожидаемыми "сайд-эффектами".
F>>К сожалению стандартной компонентной модели не существует, и весь этот ниндзя-стайл кодинг в C++ остаётся возможным только внутри модулей EP>Какой ещё "ниндзя-стайл"?
Шаблонная магия (навроде shared_ptr), которая доступна исключительно внутри модулей, но без неё нормальный API тоже не получается.
Здравствуйте, VladD2, Вы писали:
EP>>Это изменение данных inplace, изменение состояния, последовательности действий. Типичный пример ИП — это quicksort (настоящий, тот который inplace). VD>В моем коде это довольно редкое явление. Большая часть функций получается чистыми.
Они чистые внутри или снаружи? И если снаружи, то что именно ты тут понимаешь под чистотой? Например std::transform — это чистая или нет? Тут могут быть разные трактовки, поэтому я и говорю "как можно более чистыми".
— изменяемые, и предназначаются для не чистого кода, так?
VD>Лично я предпочитаю ООП для дизайна системы в целом, плюс ФП для реализации. Ну, тоже без абсолютизма. Там где удобен ИП или он дает большую производительность, использую его.
А что ты понимаешь под "ФП для реализации"? ИП + некоторые элементы характерные ФЯ? Или именно настоящие ФП без изменения данных?
EP>>Что значит "в математике"? Императивное программирование это вполне себе математика. VD>Математика вообще не подразумевает изменения переменных. Так что ты совсем не верно понимаешь математику.
С чего ты взял что математика не подразумевает изменения данных во времени? Это миф распространяемый евангелистами чистого ФП.
Машина Тьюринга — это вполне себе математика
EP>>А главное зачем? То есть мы решили — "внутри функций будем использовать ИП" — и для этого выбираем изначально чужеродный этой концепции язык и начинаем наворачивать монадический код, вместо того чтобы взять/сделать язык со встроенным, удобным и быстрым ; VD>А, какой язык ты называешь чужеродным ИП?
Например Haskell — ИП на нём делается через монады и трёхэтажные замыкания.
EP>>Интерфейс в смысле API. Функциональный интерфейс — это когда "снаружи" у нас функции, причём чистые настолько, насколько это целесообразно. VD>Какой-то мало реалистичный подход. Первая коллекция разрушит этот интерфейс.
Ты отвечаешь на моё пояснения к вот этой фразе:
EP>* Интерфейсы же лучше всего делать функциональными и как можно более чистыми.
То есть если не получается сделать функциональный — ОК, делаем контейнер или что там нужно.
VD>Вот ФП внутри методов — это реалистично и используется на практике повсюду. Даже в C#.
Это никакое не ФП, а просто некоторые элементы которые исторически характерны ФЯ, но ФП не определяющие. Против которых я кстати ничего против не имею, буквально чуть выше по ветке:
EP>То есть по сути — ФП снаружи, ИП с элементами ФЯ внутри, а ООП где-то сбоку.
Здравствуйте, fddima, Вы писали:
EP>>Это изменение данных inplace, изменение состояния, последовательности действий. Типичный пример ИП — это quicksort (настоящий, тот который inplace). F> Понятно. quicksort — это не сколько ИП, а сколько алгоритм сортировки in-place.
Суть настоящего quicksort именно в перестановке элементов in-place, это даже упоминал Erik Meijer в одном из своих видео по Haskell.
F>При чём, он сам по себе отлично реализуется и в ФЯ.
Как? Вот так? Через те самые монады и трёхэтажные замыкания?
F> Да, и, в моём коде, на C# — много методов, которые можно было бы назвать чистыми функциями. Но, чистые функции — сами по себе не самоцель.
Чистые где? Внутри или снаружи?
F>>>При этом, в C/C++ — есть оператор , который выполняет туже роль, что и ; — но для выражений. Дублирование на лицо. EP>>Я не вижу причём тут expressions vs statements. F> Сомневаюсь. Итерирования, как такового в математике пожалуй нет.
Есть. Итерирование, как и ИП в целом — чисто математическая концепция. Итерационные алгоритмы учат начиная с первых классов математики.
Другой пример — начала Евклида — книга по которой учат геометрию почти 2300 лет. В дословном переводе первые три постулата отличаются от того, как нас учат в школе "через две точки можно провести прямую ...". Например перевод Sir Thomas Heath:
Let the following be postulated:
1. To draw a straight line from any point to any point.
2. To produce a finite straight line continuously in a straight line.
3. To describe a circle with any centre and distance.
Это же императивные процедуры в чистом виде, которые изменяют глобальное состояние. Далее, на основе этих процедур строятся более крупные:
Proposition 1.
On a given finite straight line to construct an equilateral triangle.
F> Зачем монады и bind-ы я не понял. Что мешает иметь оператор ; в ФЯ, ну кроме научных комплексов? Кроме того, гибридные ФЯ именно это и позволяют.
В том-то и дело что это будет гибрид. Я как раз за гибридный подход, и против фанатизма типа "всё должно быть объектом", "всё должно быть чистым" и т.п.
F> Expressions vs statements — к тому, что statements заставляют код выглядить "императивным", даже где это не нужно. Весь привычный flow-control, кроме goto — отлично представляется ввиде выражений. Более того, опять же — практика Scala и Nemerle — показывает, что это очень удобно.
Я не спорю что это удобно. Тем не менее итерации с явным мутированием данных это ИП, даже если это сделало на expressions вместо statements
EP>>>>* Интерфейсы же лучше всего делать функциональными и как можно более чистыми. F>>> Интерфейс чего? Я не понял. И что значит функциональный интерфейс? EP>>Интерфейс в смысле API. Функциональный интерфейс — это когда "снаружи" у нас функции, причём чистые настолько, насколько это целесообразно. F> Это уже закончилось прокачкой магических хэндлов в параметры любого API, от API OS до API библиотек.
Я же прямым текстом сказал что ООП неплох в качестве поставщика типов для параметров функций.
EP>>>>То есть по сути — ФП снаружи, ИП с элементами ФЯ внутри, а ООП где-то сбоку. F>>> Если тебе кажется что все парадигмы — это лишь названия и об одном и том же, — то с философской точки зрения — это так и есть. EP>>В каком смысле? F> В том, что все парадигмы программирования — разные инструменты для достижения одной и той же цели.
А что из этого следует? Нужно свернуть все обсуждения преимуществ той или иной парадигмы так как цель-то у них одна?
F>В итоге — это выполняется на конкретном железе с ожидаемыми "сайд-эффектами".
Да, но например что-то более эффективно ложится на реалии современного железа, а что-то нет. Это крайне объективный показатель.
F>>>К сожалению стандартной компонентной модели не существует, и весь этот ниндзя-стайл кодинг в C++ остаётся возможным только внутри модулей EP>>Какой ещё "ниндзя-стайл"? F> Шаблонная магия (навроде shared_ptr), которая доступна исключительно внутри модулей, но без неё нормальный API тоже не получается.
Конкретные воплощения шаблонов можно экспортировать из динамических библиотек. Если нужно дать возможность пользователю инстанциировать шаблоны со своими типами — то нужно распространять код/headers.
Но только причём тут всё это? Я про C++, метапрограммирование и параметрический полиморфизм вроде ничего не говорил.
Да и те же Generics спокойно гуляют по .NET и JVM
Так, пожалуй надо закругляться (я изначально лишь задал уточняющие вопросы, часть которых ты и так уже уточнил, а мои фантазирования — пошли не в то русло). Поэтому, если будешь отвечать, то можно покороче.
F>>При чём, он сам по себе отлично реализуется и в ФЯ. EP>Как? Вот так? Через те самые монады и трёхэтажные замыкания? F>> Да, и, в моём коде, на C# — много методов, которые можно было бы назвать чистыми функциями. Но, чистые функции — сами по себе не самоцель. EP>Чистые где? Внутри или снаружи?
Я ж написал тоже самое. Ялгоритм сортировки in-place. Эрика зачем ты сюда призвал.
Мне лень разбираться в хаскеле, очевидно, и так, что для реализации quicksort нужны как минимум массивы и мутации.
Да и реализуется это как угодно. ! Там прямо сказано, что не везде это реализуемо в прямом смысле, о чём ты и говоришь. При этом, всё же, стоит отметить, что это ущербность ЯП, а не декларативно-функционального подхода как такового.
F>>>>При этом, в C/C++ — есть оператор , который выполняет туже роль, что и ; — но для выражений. Дублирование на лицо. EP>>>Я не вижу причём тут expressions vs statements. F>> Сомневаюсь. Итерирования, как такового в математике пожалуй нет. EP>Есть. Итерирование, как и ИП в целом — чисто математическая концепция. Итерационные алгоритмы учат начиная с первых классов математики.
Ты про деление в стобик что-ли? Это не имеет отношения к итерированию.
EP>Другой пример — начала Евклида — книга по которой учат геометрию почти 2300 лет. В дословном переводе первые три постулата отличаются от того, как нас учат в школе "через две точки можно провести прямую ...". Например перевод Sir Thomas Heath:
Есть в математике масса алгоритмов вычислить одно и тоже, но как именно это ты будешь делать вообще никого не волнует. Есть конкретные операции (операторы). Начиная от суммы рядов, в том числе и бесконечных — заканчивая не знаю даже чем, пусть будет перемножением матриц самых разных размерностей.
Алгоритмы, появляются, когда мы переходим от аналитики к конкретным вычислениям. При чём попунктно оно как раз разложено для того, что бы в это могли въехать прежде всего люди.
Численные методы — это как раз тот раздел, который по-колхозному, можно назвать "сборник алгоритмов для ЭВМ".
Геометрию же любого рода — я лично за математику не считаю. Это она — основана на математике (аналитическая её часть). Я помню как ночами рисовал фигню всякую — ничего там от математики точно нет.
F>> Зачем монады и bind-ы я не понял. Что мешает иметь оператор ; в ФЯ, ну кроме научных комплексов? Кроме того, гибридные ФЯ именно это и позволяют. EP>В том-то и дело что это будет гибрид. Я как раз за гибридный подход, и против фанатизма типа "всё должно быть объектом", "всё должно быть чистым" и т.п.
Ну так и пусть будет гибрид.
EP>Я не спорю что это удобно. Тем не менее итерации с явным мутированием данных это ИП, даже если это сделало на expressions вместо statements
Ты прав. Но, итерации тут не причём. Итерирование в ФП используется в полный рост (через рекурсию, через отрыв головы и рекурсию и т.п.). А вот мутации — да, согласен.
F>>>> Интерфейс чего? Я не понял. И что значит функциональный интерфейс? EP>>>Интерфейс в смысле API. Функциональный интерфейс — это когда "снаружи" у нас функции, причём чистые настолько, насколько это целесообразно. F>> Это уже закончилось прокачкой магических хэндлов в параметры любого API, от API OS до API библиотек. EP>Я же прямым текстом сказал что ООП неплох в качестве поставщика типов для параметров функций.
Если я правильно понял, то разницы я не понял. Я понял только, что снаружи ООП-like — удобнее выходит.
F>>>> Если тебе кажется что все парадигмы — это лишь названия и об одном и том же, — то с философской точки зрения — это так и есть. EP>>>В каком смысле? F>> В том, что все парадигмы программирования — разные инструменты для достижения одной и той же цели. EP>А что из этого следует? Нужно свернуть все обсуждения преимуществ той или иной парадигмы так как цель-то у них одна? F>>В итоге — это выполняется на конкретном железе с ожидаемыми "сайд-эффектами". EP>Да, но например что-то более эффективно ложится на реалии современного железа, а что-то нет. Это крайне объективный показатель.
Вовсе нет, напротив.
И делается то всё ради целевых платформ, и безбожно тормозящий софт использующий самые правильные парадигмы (пофигу какие) — он никому не нужен.
F>> Шаблонная магия (навроде shared_ptr), которая доступна исключительно внутри модулей, но без неё нормальный API тоже не получается. EP>Конкретные воплощения шаблонов можно экспортировать из динамических библиотек. Если нужно дать возможность пользователю инстанциировать шаблоны со своими типами — то нужно распространять код/headers. EP>Но только причём тут всё это? Я про C++, метапрограммирование и параметрический полиморфизм вроде ничего не говорил. EP>Да и те же Generics спокойно гуляют по .NET и JVM
Можно, но — за пределами C++ — это использовать толком невозможно. Например из тех же .NET и JVM. Есть конечно разной извращенности способы, но это приседания вокруг одного и того же. Я как раз и написал "к сожалению стандартной компонентной модели не существует". Разумеется внутри .NET и внутри JVM проблем таких нет.
Я и написал, что, как по мне — это (отсутствие стандартной КОМ) — куда более существенно, чем всё остальное.
Здравствуйте, fddima, Вы писали:
F> Так, пожалуй надо закругляться (я изначально лишь задал уточняющие вопросы, часть которых ты и так уже уточнил, а мои фантазирования — пошли не в то русло). Поэтому, если будешь отвечать, то можно покороче.
Как получится Это же форум, тут необязательно отвечать на всё и сразу, причём ответить можешь и не только ты.
F>>> Да, и, в моём коде, на C# — много методов, которые можно было бы назвать чистыми функциями. Но, чистые функции — сами по себе не самоцель. EP>>Чистые где? Внутри или снаружи? F> Я ж написал тоже самое. Ялгоритм сортировки in-place.
Так всё-таки, в твоём коде много каких чистых функций? У которых просто чистый интерфейс или и в реализации нет никакого мутирования?
F> Мне лень разбираться в хаскеле, очевидно, и так, что для реализации quicksort нужны как минимум массивы и мутации.
Вот они и натягиваются на чистое ФП через через монады. При этом каждый последовательный statement это под капотом замыкание вложенное в замыкание предыдущего statement'а, которое в свою очередь вложено в в замыкание предыдущего, которое и т.п.
F>При этом, всё же, стоит отметить, что это ущербность ЯП, а не декларативно-функционального подхода как такового.
ФП это как раз про избегание явного мутирования, если же оно есть — то это отход от ФП.
F>>> Сомневаюсь. Итерирования, как такового в математике пожалуй нет. EP>>Есть. Итерирование, как и ИП в целом — чисто математическая концепция. Итерационные алгоритмы учат начиная с первых классов математики. F> Ты про деление в стобик что-ли?
В том числе.
F>Это не имеет отношения к итерированию.
Ярко выраженное итерирование с изменением состояния типа "текущий разряд".
EP>>Другой пример — начала Евклида — книга по которой учат геометрию почти 2300 лет. В дословном переводе первые три постулата отличаются от того, как нас учат в школе "через две точки можно провести прямую ...". Например перевод Sir Thomas Heath: F> Есть в математике масса алгоритмов вычислить одно и тоже, но как именно это ты будешь делать вообще никого не волнует.
Математику саму по себе вообще ничего не волнует. Но построение алгоритмов, как и сами алгоритмы — это математика в чистейшем виде.
F> Алгоритмы, появляются, когда мы переходим от аналитики к конкретным вычислениям. При чём попунктно оно как раз разложено для того, что бы в это могли въехать прежде всего люди.
Конкретные вычисления — это та же математика. Даже более того — математика изначально была как раз про конкретные вычисления, и про то как совершать их быстрее.
F> Геометрию же любого рода — я лично за математику не считаю.
Геометрия — со всеми этими постулатами, аксиомами, леммами, теоремами — это математика в дистиллированном виде.
F>Это она — основана на математике (аналитическая её часть).
Ты видимо путаешь математику и алгебру, которая является частью математики, и на которую опирается аналитическая геометрия.
F> Я помню как ночами рисовал фигню всякую — ничего там от математики точно нет.
Это видимо не геометрия, а какое-нибудь черчение (начертательная геометрия) — но и там математики хоть отбавляй.
Вот есть отрезок, на отрезке нужно построить равносторонний треугольник — например это можно сделать с помощью циркуля и линейки, воспользовавшись геометрическими свойствами.
F>>> Зачем монады и bind-ы я не понял. Что мешает иметь оператор ; в ФЯ, ну кроме научных комплексов? Кроме того, гибридные ФЯ именно это и позволяют. EP>>В том-то и дело что это будет гибрид. Я как раз за гибридный подход, и против фанатизма типа "всё должно быть объектом", "всё должно быть чистым" и т.п. F> Ну так и пусть будет гибрид.
Так и я о том же
EP>>Я же прямым текстом сказал что ООП неплох в качестве поставщика типов для параметров функций. F> Если я правильно понял, то разницы я не понял. Я понял только, что снаружи ООП-like — удобнее выходит.
Взять например STL — алгоритмы и контейнеры. Контейнеров-классов — десятки, алгоритмов-функций их использующих — сотни. По-твоему это ООП-like?
EP>>Да, но например что-то более эффективно ложится на реалии современного железа, а что-то нет. Это крайне объективный показатель. F> Вовсе нет, напротив. F> И делается то всё ради целевых платформ, и безбожно тормозящий софт использующий самые правильные парадигмы (пофигу какие) — он никому не нужен.
Я не пойму с чем ты не согласен? Я говорю что эффективность это объективный показатель, ты возражаешь, но делаешь высказывание ровно в том же русле "тормозящий софт не нужен".
EP>>Но только причём тут всё это? Я про C++, метапрограммирование и параметрический полиморфизм вроде ничего не говорил. EP>>Да и те же Generics спокойно гуляют по .NET и JVM F> Можно, но — за пределами C++ — это использовать толком невозможно. Например из тех же .NET и JVM. Есть конечно разной извращенности способы, но это приседания вокруг одного и того же. Я как раз и написал "к сожалению стандартной компонентной модели не существует". Разумеется внутри .NET и внутри JVM проблем таких нет.
Внутри нет, но они точно также встают в полный рост "снаружи".
F> Я и написал, что, как по мне — это (отсутствие стандартной КОМ) — куда более существенно, чем всё остальное.
Это действительно проблема, нет даже единого стандарта вызова функций (точнее их много разных), не то что стандартных компонентов.
Да, это важно. Но разработка кода, то что "внутри функций" — это тоже важно.
Причём без стандартных компонентов ещё можно создавать программы, но без непосредственно возможности создавать код — ничего интересного вообще не получится.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>>>Чистые где? Внутри или снаружи? F>> Я ж написал тоже самое. Ялгоритм сортировки in-place. EP>Так всё-таки, в твоём коде много каких чистых функций? У которых просто чистый интерфейс или и в реализации нет никакого мутирования?
Много и тех и других, как много и мутирующих (инфраструктура в основном, да и цель — состояние).
Но (по случайному совпадению) — сейчас ключевая функциональность сосредоточена там где вообще нет никакого мутирования.
А в другом небольшом проекте — логики считай вообще нет, одна инфраструктурщина, — и считай — везде одно состояние.
F>> Мне лень разбираться в хаскеле, очевидно, и так, что для реализации quicksort нужны как минимум массивы и мутации. EP>Вот они и натягиваются на чистое ФП через через монады. При этом каждый последовательный statement это под капотом замыкание вложенное в замыкание предыдущего statement'а, которое в свою очередь вложено в в замыкание предыдущего, которое и т.п.
Т.е. ресурсы это может сожрать в один момент.
F>>При этом, всё же, стоит отметить, что это ущербность ЯП, а не декларативно-функционального подхода как такового. EP>ФП это как раз про избегание явного мутирования, если же оно есть — то это отход от ФП.
Правильно. Я говорю о ЯП. Когда ЯП тебя ограничил, а тебе нужно именно так — получается взяли вилы (C/C++) в руки, накостыляли что нужно, и прибиндили чего-нибудь. Мне такой подход в ЯП (ограниченный) — решительно не нравится.
. . .
F>> Я помню как ночами рисовал фигню всякую — ничего там от математики точно нет. EP>Это видимо не геометрия, а какое-нибудь черчение (начертательная геометрия) — но и там математики хоть отбавляй.
Она (начертательная). Ну математика есть везде, тут спорить смысла нет. Просто, мне казалось, её там и не так много (хотя может и забылось).
EP>Вот есть отрезок, на отрезке нужно построить равносторонний треугольник — например это можно сделать с помощью циркуля и линейки, воспользовавшись геометрическими свойствами.
Угу. Мне под математикой легче понимать аналитические её части (в том числе и геометрию). А с "обычной" геометрией, как-то не сложилось подружиться.
EP>Так и я о том же
EP>>>Я же прямым текстом сказал что ООП неплох в качестве поставщика типов для параметров функций. F>> Если я правильно понял, то разницы я не понял. Я понял только, что снаружи ООП-like — удобнее выходит. EP>Взять например STL — алгоритмы и контейнеры. Контейнеров-классов — десятки, алгоритмов-функций их использующих — сотни. По-твоему это ООП-like?
Ну да, — по-моему это ООП-like.
EP>Я не пойму с чем ты не согласен? Я говорю что эффективность это объективный показатель, ты возражаешь, но делаешь высказывание ровно в том же русле "тормозящий софт не нужен".
Показалось, что то была ирония. Так что согласен.
F>> Можно, но — за пределами C++ — это использовать толком невозможно. Например из тех же .NET и JVM. Есть конечно разной извращенности способы, но это приседания вокруг одного и того же. Я как раз и написал "к сожалению стандартной компонентной модели не существует". Разумеется внутри .NET и внутри JVM проблем таких нет. EP>Внутри нет, но они точно также встают в полный рост "снаружи".
Ну, так я и говорю о проблемах снаружи. .NET/Java/C++ — я взял просто как примеры инородных "сред".
F>> Я и написал, что, как по мне — это (отсутствие стандартной КОМ) — куда более существенно, чем всё остальное. EP>Это действительно проблема, нет даже единого стандарта вызова функций (точнее их много разных), не то что стандартных компонентов. EP>Да, это важно. Но разработка кода, то что "внутри функций" — это тоже важно. EP>Причём без стандартных компонентов ещё можно создавать программы, но без непосредственно возможности создавать код — ничего интересного вообще не получится.
Тут ты прав конечно. Но из-за того, что, в наше время — мы в 99.9% становимся потребителями чужого кода / библиотек — мне это видится важным. Хотя, это ортогонально к парадигмам. Собственно говоря почему всё оно именно так — понятно. Просто грустно.
EP>>>А почему для лямбд это трудно, а для local functions нет? В чём разница? Лямбды завязаны на какое-то ABI jit'а? S>>Неа, потому что лямбды — это делегаты, их значение может меняться в рантайме. EP>В каком смысле? Что это даёт по сравнению с локальными функциями?
void Do(Action a)
{
a(); // вызываемый метод неизвестен при компиляции
}
Вперёд, оптимизируйте на здоровье
И не забыть про a.Target, a.BeginInvoke() etc.
Тут разве что .net native поможет, там всё как раз в один бинарник собирается. Но до оптимизации .net native руки дойдут ооочень не скоро. Как минимум не в следующем релизе.
S>>Локальные функции ничем не отличаются от вызова приватного метода, для них отдельных приседаний делать не надо. EP>Насколько я понял по форме отличий мало, отличаются реализации. Что мешает для старой формы сделать быструю реализацию?
Возможнось передать лямбду в чужой код.
EP>Тут просто часто говорят что лямбды в C# медленные, постоянные аллокации и т.п. — по сути стоит выбор между удобством и скоростью — я вот и не понимаю почему их не оптимизировать в компиляторе, чтобы не было такой дилеммы там где её быть не должно.
Вы их больше слушайте. Чтобы уткнуться в лямбды — надо оччень сильно постараться, причём писать код не включая мозг там будет обязательным условием. Тынц
Здравствуйте, Sinix, Вы писали:
S>Возможнось передать лямбду в чужой код.
А для замыкающей вложенной функции не предполагается передача в чужой код?
(Дайте мне просто вложенные функции как сахар для лямбд, а то придумали оптимизации-шмоптимзации.)
EP>>Тут просто часто говорят что лямбды в C# медленные, постоянные аллокации и т.п. — по сути стоит выбор между удобством и скоростью — я вот и не понимаю почему их не оптимизировать в компиляторе, чтобы не было такой дилеммы там где её быть не должно. S>Вы их больше слушайте. Чтобы уткнуться в лямбды — надо оччень сильно постараться, причём писать код не включая мозг там будет обязательным условием. Тынц
Здравствуйте, Qbit86, Вы писали:
S>>Возможнось передать лямбду в чужой код. Q>А для замыкающей вложенной функции не предполагается передача в чужой код?
Только как делегат
Q>(Дайте мне просто вложенные функции как сахар для лямбд, а то придумали оптимизации-шмоптимзации.)
Давайте с ломающими изменениями в какой-нибудь другой язык
EP>>>А создание лямбды дорогое? S>>2.31*108 calls per sec. Хватит? Q>В дотнетовском мемори-трафике, как обычно, всё-таки не так страшны аллокации, как сборка мусора.
Угу, при неправильном использовании вполне можно отхватить вот такое
Здравствуйте, desco, Вы писали:
IT>>* Сравни АДТ и ПМ в F# и Немерле. В F# это выглядит как убогая пародия. D>А можно расписать по пунктам, в чем состоит убогость PM в F#?
АТД потенциально может быть довольно сложной структурой, например, такой как Expression Tree в .NET. Т.е. полноценной плоской иерархией классов со всеми блекжеками и шлюхами в виде наследования как базовой функциональности, так и данных. Ничего этого в F# нет. К тому же я не понял, что за косяк у них там с именованными полями. Получается, если я объявляю именованные поля, то должен всегда использовать имена, позиционирование не работает?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
EP>>* Интерфейсы же лучше всего делать функциональными и как можно более чистыми. IT>Функциональные интерфейсы?
Что смущает? У слова "интерфейс" более широкое значение нежели у "interface" в ООП
EP>>То есть по сути — ФП снаружи, ИП с элементами ФЯ внутри, а ООП где-то сбоку. IT>Теперь понятно, что смущает в твоих рассуждениях. Фактически ты одни ФП фишки низводишь до уровня некоторых элементов характерных ФЯ,
Каковыми они и являются. Отсутствие тех или иных фишек в языках которые называют функциональными, плюс присутствие таких фишек даже в старых императивных языках, как раз и говорит о том что эти фишки не определяющие для ФП.
IT>а другие возносишь до уровня определяющего парадигму и дальше делаешь далеко идущие выводы
Что поделать если они действительно определяют ФП. Да хотя бы посмотри определение ФП в той же wiki.
Мысли типа "я вот тут лямбду добавил и PM, теперь тело метода ФП!!11" — не более чем заблуждение. На что указывают даже евангелисты ФП:
K>Вообще, какие-то функциональные фишечки эпизодически используются почти везде. Но использование ПМ делает подход функциональным не в большей степени, чем использование иммутабельных строк, например. ФП — это комбинирование комбинаторов как основной способ решения задач программирования, а не "я тут как-то раз фильтр использовал вместо цикла".
IT>В принципе, так можно договориться до того, что ФП — это ООП, а ООП — это ФП, т.к., например, достаточно выдержать средствами ООП или императивщиной чистоту функции и вот у нас уже не ООП, а чистейший ФП.
Если ты сделаешь чистые функции без мутирования данных — то как раз и получишь ФП в чистом виде
IT>Давай подойдём к впоросу с другой стороны. Любая парадигма сопровождается практическими подходами, набором паттернов и инструментов.
Которые опираются на конкретные возможности языка. Есть ФП языки вообще без лямбд, и в которых естественно не может быть никаких практических подходов на этих самых лямбдах.
IT>Если разобраться где это всё рулит лучше всего, то окажется, что: IT>* Императив внутри метода, как я уже говорил, рулит только говнокодом.
Какая аргументация?
IT>Бедность императивных конструкций и подходов можно наблюдать в том же C# до 3-й версии.
C# это изначально бедный язык, причём в первых версиях копирующий другой бедный язык. Что тем не менее не что означает всё ИП бедное.
IT>* Настоящее счастье внутри метода достигается только использованием инструментов и подходов из функционального программирования.
Эпизодическое смешивание императивного кода с элементами характерными ФЯ, или даже с элементами определяющими ФП — не делают тела методов функциональными
IT>* Компоновка методов в более крупные образования делается с помощью ООП.
Во внутрь объектов имеет смысл "компоновать" только код который не может быть эффективно реализован через публичный интерфейс — остальное удобнее и даже лучше с точки зрения инкапсуляции делать как обычные внешние функции (которых так стесняются Java и C#).
В итоге получаем функции + классы. А компоновка происходит в namespace'ы и модули.
IT>Итого: ФП рулит внутри, ООП снаружи.
Да нет же. ИП приправленное щепоткой фишек ФЯ (по всей видимости это то о чём ты говоришь) не делает превращает его в ФП
IT>Правильные подходы и практики рулят везде.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
IT>>Функциональные интерфейсы? EP>Что смущает? У слова "интерфейс" более широкое значение нежели у "interface" в ООП
У детерминированности и отсутствии побочных эффектов тоже более широкое значение чем pure function в фукциональном программировании. Тем не менее тебе это не мешает делать вывод, что если функция удовлетворяет этим двум условиям, то мы однозначно имеем дело с ФП
IT>>Теперь понятно, что смущает в твоих рассуждениях. Фактически ты одни ФП фишки низводишь до уровня некоторых элементов характерных ФЯ, EP>Каковыми они и являются. Отсутствие тех или иных фишек в языках которые называют функциональными, плюс присутствие таких фишек даже в старых императивных языках, как раз и говорит о том что эти фишки не определяющие для ФП.
Тем не менее до недавнего времени они встречались только либо в ФЯ, либо в гибридах.
IT>>а другие возносишь до уровня определяющего парадигму и дальше делаешь далеко идущие выводы EP>Что поделать если они действительно определяют ФП. Да хотя бы посмотри определение ФП в той же wiki.
Под определение в вики подходит даже ассемблер.
EP>Мысли типа "я вот тут лямбду добавил и PM, теперь тело метода ФП!!11" — не более чем заблуждение. На что указывают даже евангелисты ФП:
Больших коньюнктурщиков чем всякого рода проповедников не существует. За последние 10 лет евангелисты ФП перекрасились уже во все цвета радуги. А ведь когда-то у них был один простой ответ на все вопросы про ФП — "Вы ничего не понимаете!". А теперь оказывается "функциональные фишечки эпизодически используются почти везде". А 10 лет назад почему они не использовались почти везде?
IT>>В принципе, так можно договориться до того, что ФП — это ООП, а ООП — это ФП, т.к., например, достаточно выдержать средствами ООП или императивщиной чистоту функции и вот у нас уже не ООП, а чистейший ФП. EP>Если ты сделаешь чистые функции без мутирования данных — то как раз и получишь ФП в чистом виде
А мужики-то и не знают. Оказывается я с самого первого дня писал в функциональном стиле. Особенно на Фортране и PL/1. Ага. Достаточно написать метод, который умножет два на три и вот уже оно ФП в полный рост.
IT>>* Императив внутри метода, как я уже говорил, рулит только говнокодом. EP>Какая аргументация?
Какая?
IT>>Бедность императивных конструкций и подходов можно наблюдать в том же C# до 3-й версии. EP>C# это изначально бедный язык, причём в первых версиях копирующий другой бедный язык. Что тем не менее не что означает всё ИП бедное.
Видимо у нас с тобой разное ИП. Богатого ИП я пока не видел. Разбавленное функциональными фишками видел. Богатое — не видел.
IT>>* Настоящее счастье внутри метода достигается только использованием инструментов и подходов из функционального программирования. EP>Эпизодическое смешивание императивного кода с элементами характерными ФЯ, или даже с элементами определяющими ФП — не делают тела методов функциональными
Но, как я понимаю выдерживание принципов ФП снаружи не взирая на любое ИП внутри легко делают методы функциональными?
EP>остальное удобнее и даже лучше с точки зрения инкапсуляции делать как обычные внешние функции (которых так стесняются Java и C#).
При чём здесь стесняются? Код традиционно удобнее компоновать в классы. В C# для того о чём ты говоришь существуют статические классы. Можешь их объявлять хоть по одному на каждую такую внешнюю функцию. Только какой в этом смысл?
IT>>Итого: ФП рулит внутри, ООП снаружи. EP>Да нет же. ИП приправленное щепоткой фишек ФЯ (по всей видимости это то о чём ты говоришь) не делает превращает его в ФП
Я нигде ни разу не сказал про превращения. Мне вообще больше всего импонирует смешанный стиль и возможность выбирать по месту по потребностям и не парить себе мозг бредом типа насколько мой код соответствует или не соответствует той или иной парадигме.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>>>Функциональные интерфейсы? EP>>Что смущает? У слова "интерфейс" более широкое значение нежели у "interface" в ООП IT>У детерминированности и отсутствии побочных эффектов тоже более широкое значение чем pure function в фукциональном программировании. Тем не менее тебе это не мешает делать вывод, что если функция удовлетворяет этим двум условиям, то мы однозначно имеем дело с ФП
У детерминированности и отсутствии побочных эффектов действительно более широкое значение чем у pure function. Тем не менее, когда наш код целиком состоит из pure functions — то да, мы имеем дело с ФП, как минимум с чем-то крайне близким к ФП — это следует из самого определения ФП.
IT>>>Теперь понятно, что смущает в твоих рассуждениях. Фактически ты одни ФП фишки низводишь до уровня некоторых элементов характерных ФЯ, EP>>Каковыми они и являются. Отсутствие тех или иных фишек в языках которые называют функциональными, плюс присутствие таких фишек даже в старых императивных языках, как раз и говорит о том что эти фишки не определяющие для ФП. IT>Тем не менее до недавнего времени они встречались только либо в ФЯ, либо в гибридах.
Лямбды-замыкания были в ООП-шном Smalltalk, а ФВП были даже в императивном C.
IT>>>а другие возносишь до уровня определяющего парадигму и дальше делаешь далеко идущие выводы EP>>Что поделать если они действительно определяют ФП. Да хотя бы посмотри определение ФП в той же wiki. IT>Под определение в вики подходит даже ассемблер.
Крайне ограниченное подмножество.
IT>>>В принципе, так можно договориться до того, что ФП — это ООП, а ООП — это ФП, т.к., например, достаточно выдержать средствами ООП или императивщиной чистоту функции и вот у нас уже не ООП, а чистейший ФП. EP>>Если ты сделаешь чистые функции без мутирования данных — то как раз и получишь ФП в чистом виде IT>А мужики-то и не знают. Оказывается я с самого первого дня писал в функциональном стиле. Особенно на Фортране и PL/1. Ага.
Если не было никакого явного мутирования данных (в чём я сомневаюсь), функции были чистыми — то да, это был функциональный стиль.
IT>Достаточно написать метод, который умножет два на три и вот уже оно ФП в полный рост.
А что смущает?
IT>>>* Императив внутри метода, как я уже говорил, рулит только говнокодом. EP>>Какая аргументация? IT>Какая?
Несколько раз повторяешь что ИП внутри метода плох. Хотелось бы услышать аргументы, так как по моим представлением именно ИП (пусть и разбавленный фишками исторически характерными скорее ФЯ) сейчас лучше всего подходит для тел функций.
IT>>>* Настоящее счастье внутри метода достигается только использованием инструментов и подходов из функционального программирования. EP>>Эпизодическое смешивание императивного кода с элементами характерными ФЯ, или даже с элементами определяющими ФП — не делают тела методов функциональными IT>Но, как я понимаю выдерживание принципов ФП снаружи не взирая на любое ИП внутри легко делают методы функциональными?
Думаю что нет — для полного ФП нужны и ФП интерфейсы и ФП внутренности.
IT>>>* Компоновка методов в более крупные образования делается с помощью ООП. EP>>Во внутрь объектов имеет смысл "компоновать" только код который не может быть эффективно реализован через публичный интерфейс — остальное удобнее и даже лучше с точки зрения инкапсуляции делать как обычные внешние функции (которых так стесняются Java и C#). EP>>В итоге получаем функции + классы. А компоновка происходит в namespace'ы и модули. EP>>остальное удобнее и даже лучше с точки зрения инкапсуляции делать как обычные внешние функции (которых так стесняются Java и C#). IT>При чём здесь стесняются? Код традиционно удобнее компоновать в классы.
А при том что компоновка это про модули и namespace'ы, а не про ООП и классы.
IT>В C# для того о чём ты говоришь существуют статические классы. Можешь их объявлять хоть по одному на каждую такую внешнюю функцию. Только какой в этом смысл?
Я как раз об этом и говорю, из-за своего врождённого стеснения функций, вместо "функций namespace'ов", получаются уродцы типа "статические методы статических классов namespace'ов" — Костыль Воркэраундович От того что namespace'ы по сути обозвали статическими классами — это не делает такую компоновку ООП-шной.
Кстати, эти статические классы ведь даже импортировать нельзя в C#
IT>>>Итого: ФП рулит внутри, ООП снаружи. EP>>Да нет же. ИП приправленное щепоткой фишек ФЯ (по всей видимости это то о чём ты говоришь) не делает превращает его в ФП IT>Я нигде ни разу не сказал про превращения.
Насколько я понял под "ФП рулит внутри" — ты понимаешь именно ИП приправленное щепоткой фишек ФЯ — так?
Или ты имеешь в виду именно "ФП внутри" в чистом "Haskell'овском" смысле?
IT>Мне вообще больше всего импонирует смешанный стиль и возможность выбирать по месту по потребностям и не парить себе мозг бредом типа насколько мой код соответствует или не соответствует той или иной парадигме.
Здравствуйте, samius, Вы писали:
EP>>У детерминированности и отсутствии побочных эффектов действительно более широкое значение чем у pure function. Тем не менее, когда наш код целиком состоит из pure functions — то да, мы имеем дело с ФП, как минимум с чем-то крайне близким к ФП — это следует из самого определения ФП. S>pure function накладывает ограничение на наблюдаемое извне поведение функции, а не на то, в какой парадгиме она написана внутри. Ровно поэтому "когда наш код целиком состоит из pure functions" — не гарантирует близости к ФП, т.к. тела функций могут быть 100% императивны.
Да, согласен — пропустил часть про явное мутировавшие данных. Дальше по сообщению оно есть.
IT>>>А мужики-то и не знают. Оказывается я с самого первого дня писал в функциональном стиле. Особенно на Фортране и PL/1. Ага. EP>>Если не было никакого явного мутирования данных (в чём я сомневаюсь), функции были чистыми — то да, это был функциональный стиль. S>Чистота функций накладывает ограничение на наличие явного мутирования данных только в том случае, когда данные могут быть восприняты в качестве побочного эффекта.
Явное мутирование внутри функции и чистота функции снаружи — как ты уже заметил, ортогональные вещи. Для ФП нужно и то, и то.
S>То есть выходит что разница между полным ФП и "чем-то крайне близким к ФП" во внутренностях? Выходит, что внутренности крайне незначительны?
Нет, конечно же внутренности значительны. Я поэтому и выделяю отдельно "функциональные интерфейсы" из "полного ФП".
Здравствуйте, Sinclair, Вы писали:
S>>Только как делегат S>Удачи в объяснении девелоперам причин, почему локальную функцию можно передать в Lync to Objects и нельзя в Lync to Sql.
А сейчас, простите, разве что-то не точно так же? То, что можно передать в l2o внезапно кидает "L2Entities does not recognize the method 'XXXX' method, and this method cannot be translated into a store expression."
Ничё, как-то "девелоперы" живут с этим.