Здравствуйте, 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>Там править нечего практически, стоимость уже сопоставима с вызовом без инлайнинга емнип. Где-то у меня был пример, надо будет найти.
Здравствуйте, Sinix, Вы писали:
S>О! А можно подробности?
Я деталей не помню, но помню, что там было все не просто. Если не ошибаюсь, проблемы возникают кода дженерик-фукции вложены в другие дженерики (имеют неявный захват дженерик-параметров).
S>Ну а зачем в типовом скрипте что-то кроме локальных функций? Плюс, если вводить методы — клиентам надо будет прописывать сигнатуру основного метода.
Скрипт, скрипту рознь. В общем, случае скрипт может отличаться от серьезного приложения только тем, что его запускают как скрипт.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, IT, Вы писали:
IT>Во-вторых, функциональная парадигма органично дополняет все остальные парадигмы включая ООП. Её место внутри метода, а снаружи весьма неплохо рулят другие.
Внутри функций рулит императивный код. Отдельные элементы исторически характерные функциональным языкам типа ФВП, замыканий, АТД, PM и т.п. — действительно удобны, но они никак не определяют функциональную парадигму.
ФП это прежде всего чистота (которая кстати скорее про внешнюю сторону функций, а не внутреннюю), а не какие-нибудь замыкания (их наоборот в ФП стараются не использовать — point-free style).
Здравствуйте, IT, Вы писали:
IT>Копипаста и дублирование — это когда копипаста и дублирование десятков и сотен строк кода. Копипаста 5-ти строчек кода — это не копипаста.
Критерий всё-таки другой. Дублирование — это такая копипаста, которая приводит к аномалиям редактирования. Если вместо вынесения общего функционала (в том числе пятистрочного) я его копипащу, то при редактировании мне нужно поправить все места пасты, это чреватое ошибками дублирование.
Если копипаста приводит к созданию новых независимых экзмпляров кода, то это не дублирование, это временное кажущееся визуальное совпадение разных частей кода. Изменение одной части не должно приводить к изменению другой части.
Например, у меня функция работает с коллекцией элементов и внутри отбирает «квадратные» и «синие». Я могу либо завести локальную функцию для фильтра, либо (если не требуется замыкание) вынести в приватный метод-хелпер. Если моему коллеге нужен аналогичный фильтр, то во втором случае он его может переиспользовать; а в первом случае реализует повторно или копипастит.
Вот только в случае вложенной функции я всегда волен при необходимости поменять фильтр и отбирать ещё и «теплые» элементы. В случае расшаренного функционала я могу случайно либо сломать тех пользователей фильтра, которым нужны не только «тёплые», либо мне придётся «отдать» владение своим фильтром и писать новый, ни от кого (пока) не зависящий.
В общем все те твои рассуждение про изолированность частей кода, про инкапсуляцию и локальность.
Здравствуйте, 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>Критерий всё-таки другой. Дублирование — это такая копипаста, которая приводит к аномалиям редактирования. Если вместо вынесения общего функционала (в том числе пятистрочного) я его копипащу, то при редактировании мне нужно поправить все места пасты, это чреватое ошибками дублирование.
Копипаста это всегда дублирование. То что ты описываешь как "копипста без дублирования" — для этого больше подходит понятие "истина"/"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) А. Эйнштейн
Здравствуйте, Qbit86, Вы писали:
Q>Небольшие классы-команды никогда не будут более легко читаемы, чем лямбды или локальные функции, если им надо захватывать замыкаемый контекст в конструкторе, или наоборот вытаскивать модифицируемые поля/свойства наружу, чтобы внешний скоуп их использовал вместо локальных переменных (как это происходит в коде, генерируемом компилятором из лямбд).
По-моему, все как раз наоборот — они всегда будут более легко читаемы, чем огромные функции с лямбдами или вложенными функциями.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
IT>>Во-вторых, функциональная парадигма органично дополняет все остальные парадигмы включая ООП. Её место внутри метода, а снаружи весьма неплохо рулят другие. EP>Внутри функций рулит императивный код.
Особенно хорошо императив рулит говнокодом.
EP>Отдельные элементы исторически характерные функциональным языкам типа ФВП, замыканий, АТД, PM и т.п. — действительно удобны, но они никак не определяют функциональную парадигму.
Совершенно верно. Наследование, инкапсуляция и полиморфизм, особенно по отдельности, тоже никак не определяют парадигму ООП. Это просто набор удобных инструментов.
EP>ФП это прежде всего чистота (которая кстати скорее про внешнюю сторону функций, а не внутреннюю), а не какие-нибудь замыкания (их наоборот в ФП стараются не использовать — point-free style).
Внешняя чистота функции определяется прежде всего её внутренней реализацией, которой как раз и достигаются детерминированность и отсутствие побочных артефактов.
Если нам не помогут, то мы тоже никого не пощадим.
Со всем согласен.
Q>В общем все те твои рассуждение про изолированность частей кода, про инкапсуляцию и локальность.
Это всё не более чем терминологические заклинания. Изоляция и инкапсуляция — это вообще-то одно и тоже, суть — сокрытие деталей. А что ты понимаешь под локальностью я не совсем понял. Пояснишь?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>А что ты понимаешь под локальностью я не совсем понял. Пояснишь?
Локальность: область видимости переменной (в том числе вложенной функции) должна быть минимальной; объявление должно быть подтянуто как можно ближе к использованию; по возможности в максимально вложенный блок (scope). Вплоть до того, что можно создать искусственный блок, намеренно ограничивающий область видимости временной переменной. В некоторых языках есть естественное органичение области видимости посредством let и where
Если в коде легко выцепить, в какой области переменная видна, и как долго «живёт», то это упрощает понимание и сопровождение кода. Снижает риски при рефакторинге.
Здравствуйте, 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>По-моему, все как раз наоборот — они всегда будут более легко читаемы, чем огромные функции с лямбдами или вложенными функциями.
Забавно, но у вас — всё наоборот. Вы хоть с кем-нибудь согласны?