Комментарии 63
Очень классно про замыкания написано тут
Обычная нотация "вход => выход", что в этом "невидимого"?
А вот замыкания — это синтаксический сахар, неявно расширяющий "вход" т.е. передающий значения/ссылки через "чёрный ход", без их явного перечисления в списке входных параметров.
Просто нужно воспринимать это не как процедуру, возращающую значение, а как математическую функцию, которая отображает входы на выходы, например f(x,y) => sin(x)*cos(y)
Вас не обязывают писать сокращения. Вы должны понимать их, но если вас как и меня они иногда вгоняют в ступор, перепишите на полный вариант со скобками и возвратом. Профит.
let food = 'cheese';
function eat() {
console.log(food + ' is good');
}
eat();
function liveADay() {
let food = 'cheese';
function eat() {
console.log(food + ' is good');
}
eat();
}
liveADay();
Прочтите оба предыдущих примера кода и убедитесь в том, что они эквивалентны.
Куски кода не являются эквивалентными. В первом случае создается глобальная переменная food. Когда во втором коде эта переменная пропадает после окончания вызова liveADay.
Так как функция eat находится внутри функции liveADay, eat «видит» все переменные, объявленные в liveADay. Именно поэтому функция eat может прочитать значение переменной food. Это и называется замыканием.
фигня. вложенные функции даже в Pascal есть, а толку. замыкание — это когда переменные и аргументы функции остаются доступными после её завершения.
при страничной организации памяти лазить в соседнюю страничку за переменной, не самая здравая идея
Хм, а почему? И как вообще понять, в какой странице лежит переменная?
А уж использование переменных объявленных внутри функции вне этой функции прям совсем моветон.
Для современных языков программирования это не "моветон", а просто невозможное действие.
Да и «предположим вам некую часть кода надо выполнить два раза подряд, поэтому можно скопипастить» вызывает судороги.Иногда небольшой кусок кода и вправду лучше скопировать, чем усложнять код и терять время.
Дэну больше написать не о чем? Я бы почитал — как хуками через свойства класса пользоваться.
Он пишет о разных вещах, включая реакт хуки. Другое дело, что не все его статьи переводятся на хабре. Подпишитесь на него на медиуме или личном сайте и читайте на здоровье (на инглише конечно же).
Вот именно. И прочитать об этом негде, а хочется. И вот так бы писать хотелось бы тоже:
export default class MyComponent extends Component {
render () {
const {
app_context: {greatest_variable},
other_context: {other_variable},
custom_variable
} = this
return (
<>
<div>{greatest_variable}</div>
<div>{other_variable}</div>
<div>{custom_variable}</div>
</>
)
}
get custom_variable() {
const {
app_context: {count: app_count},
other_context: {count: other_count},
} = this
return app_count + other_count
}
app_context = useContext(my_super_context)
other_context = useContext(my_other_context)
}
*Это синтетический пример кода. В настоящее время в нём поддерживается всё кроме хуков.
По поводу твоего примера, я бы так написал:
const MyComponent = () => {
const {greatest_variable, count: app_count} = useContext(my_super_context)
const {other_variable, count: other_count} = useContext(my_other_context)
const custom_variable = app_count + other_count
return (
<>
<div>{greatest_variable}</div>
<div>{other_variable}</div>
<div>{custom_variable}</div>
</>
)
}
export default MyComponent
И короче, и более предсказуемо, и легче читается. Ну а совмещать хуки с классами не получится, т.к. они изначально позиционировались как замена классам. У классов же есть всё, что предлагают хуки, не вижу смысла пытаться это совмещать.
Хуки завезли не чтобы классы заменить, а чтобы дать возможность использовать возможности классов в функциональных компонентах.
Сейчас перекос в другую сторону — теперь функциональные компоненты имеют больше возможностей чем классы. Но это не значит, что теперь можно смело выкинуть классы как рудимент — это по прежнему отличный инструмент для организации кода.
И в чём же заключается надругательство над концепцией? Вы случайно не путаете область видимости и время жизни?
Но в чём надругательство над концепцией-то? Переменная объявлена в некотором блоке кода — переменная видна внутри этого блока кода, и не важно сколько там вложенных блоков внутри, и являются ли эти блоки функциями...
Со стеком области видимости концептуально никак не связаны, это всего лишь детали реализации. Ну а "висение в памяти неизвестно сколько" — это общее свойство любых объектов в языках со сборщиком мусора.
А с чего это функция не функция?
Суть функции не только в том, что она возвращает какое-то значение, но и в том, что на входе она получает ограниченный набор данных — только аргументы.
Первый раз слышу о такой сути. Как минимум функции в любых языках программирования имеют доступ к глобальным переменным (за исключением языков где нет глобальных переменных) — а это уже выходит за рамки вашего "ограниченного набора данных". Родительский скоуп — он такой же, как и глобальный скоуп, только не глобальный.
Вот только родительский скоуп не обладает недостатками глобальных переменных.
Напомню почему глобальные переменные ругают:
- конфликты имён — мимо, переменных в родительском скоупе в нормальной программе не настолько много;
- непонятно где используются — мимо, если не писать километровые функции то всё понятно;
- нельзя сбросить в начальное состояние или сделать копию при необходимости — мимо, ничто не мешает вызвать родительскую функцию ещё раз.
время жизни как у глобальных — т.е. вечное, пока исполняется программа.
С фига ли?
Столько, сколько время жизни у функции (если она одна, конечно), которая замкнута на эти переменные. Что, в общем-то, логично.
Во-первых, довольно странно вешать обработчик на кнопку, которая никогда не нажмётся. Обычно предполагается, что на эту кнопку все-таки пользователь может и нажать.
Во-вторых, если ваш обработчик нажатия на кнопку использует некоторые данные — вам эти данные необходимо держать в памяти независимо от используемых технологий. Хоть на ассемблере пишите, а память всё равно будет использоваться.
время жизни как у глобальных — т.е. вечное, пока исполняется программа.
Это в какой-то конкретной имплементации, возможно, так. Но вот как раз концептуально это не так.
GC не тронет мусор если на него есть ссылки. А вот забота о том, чтобы не оставалось ссылок на ненужные объекты — ваша.
Глобальная переменная не обязательно должна жить вечно.
Её например можно удалить:
delete window.my_global_variable
В следующем раунде GC заметит, что на данные больше никто не ссылается и удалит их из памяти.
Стоит учитывать, что объекты записываются по ссылке. И если удалить ссылку (например в window), сам объект продолжит существовать. Если в другом месте приложения по прежнему присутствует ссылка на объект — сборщик мусора пройдёт мимо.
Есть масса способов выстрелить себе в ногу, помешав сборщику мусора делать свою работу. В том числе не очевидные.
С замыканиями примерно та же история
Неа, с замыканиями нет "примерно той же истории". Хорошо сделанные (не знаю, как конкретно в JS) замыкания — это то, на чем (частично) держится удобный читаемый функциональный код. Типовой пример:
User GetUserByName(string name)
{
return _users.Single(u => u.Name == name);
}
В данном случае Single
— это общеупотребимая функция с одним параметром вида T -> bool
. Ну и как это нормально переписать без замыканий?
«Читаемо» — ну это уже на вкус и цвет, как говорится. На мой взгляд, например, не очень.
А как сделать лучше, только чтобы оно выполняло ту же задачу и было консистентно?
Меня больше беспокоит, не как это написать, а как это исполнять.
Я не знаю, как в JS, а в C#, откуда этот пример взят, это "исполнять" тривиально: на этапе компиляции создается анонимный класс, в поля которого записываются все захваченные переменные, функция становится его единственным методом, этот метод вызывается. Все, профит.
Но, например, в PHP для анонимной функции нужно явно указывать, какие локальные переменные в нее передавать — это мне больше нравится.
А что в этом хорошего, кроме визуального шума?
но в этих захваченных переменных могут быть ссылки на большие ненужные объекты, которые так и останутся висеть в памяти, даже если из них нужно лишь одно свойство.
А чем это отличается от ситуации "передали большой ненужный объект как параметр"? Да ничем. Не захватывайте большие ненужные объекты, захватывайте значение нужного свойства.
Дисциплинирует
А какая мне польза от этой дисциплины?
вносит ясность для разработчика и компилятора.
А компилятору в примере выше и так понятно, какая переменная захвачена, зачем ему это дополнительно объяснять? А если понятно компилятору — то понятно и разработчику, мне IDE наглядно показывает, где переменная объявлена.
Ну это надо осознанно и умышленно идти против здравого смысла.
Ну так и при захвате большого объекта надо осознанно и умышленно идти против здравого смысла. Не вижу разницы.
Нет, при захвате можно правда очень больно стукнуться, когда время выполнения отличается, но разница с передачей параметров не такая большая, как можно было бы подумать.
_users.Single(u => u.Name == name);
Это кстати удобный читаемый функциональный — ООП
А так будет без замыканий и функционально:
function getUserByName(name, users)
{
for(let user of users) {
if(user?.name === name) {
return user
}
}
}
А так будет без замыканий
Да я тоже могу написать без замыканий. Только переиспользовать кусок кода
if(user?.name === name) {
return user
}
не получится. А смысл именно в этом. Собственно, вот вам чистое функциональное:
getUserByName name users = users |> tryFirst (fun u -> u.name == name)
u -> u.name
Точка для доступа к свойству объекта. Предлагаю признать ничью.
Так я тонко хотел подвести к тому, что незачем ударяться в фанатичное использование\игнорирование особенностей языка.
Например ФП и ООП отлично дополняют друг друга.
Всему своё место и время.
Точка для доступа к свойству объекта
Неа. Это record, а не объект.
Так я тонко хотел подвести к тому, что незачем ударяться в фанатичное использование\игнорирование особенностей языка.
Вам это не удалось.
Хорошо. Вы победили. Спасибо за ликбез.
Замечу только, что в челендже мы написали ортодоксальный, но плохо читаемый код в угоду ортодоксу.
А начиналось то всё с хорошо читаемого кода, хоть и с сахаром, ООП, замыканиями… и чего там ещё не в ходу у ортодокса, но хорошо — читаемого.
За чистоту кода, а не подхода.
Замечу только, что в челендже мы написали ортодоксальный, но плохо читаемый код в угоду ортодоксу.
Вы? Возможно, не буду судить ваш код. Но вот мой код — совершенно типовой для обоих языков, и в рамках своего языка прекрасно читается.
Точнее даже как, в обоих случаях код можно еще сократить и сделать еще более идиоматичным (и более читаемым для тех, кто язык знает), но это было бы менее понятно в данной дискуссии, где я ориентировался на тех, кто язык не знает.
Точка для доступа к свойству объекта. Предлагаю признать ничью.
Ну вот вам без точек для доступа к объекту:
import qualified Data.List.Safe as List
getUserByName name users = List.head $ filter (\u -> Name u == name) users
Замыкание — это то, что здесь описано в "призраке вызова функции". В предыдущих примерах только доступ к внешним переменным.
Замыкания — это зло. При частом использовании создают абсолютно не читаемый код… Как раз сейчас такой ковыряю…
Дэн Абрамов о замыканиях в JavaScript