Константы и присвоение. Переменные - не объекты.

Программист Ruby 2 место Готв Победитель Сбитой кодировки Учитель Оратор
Больше
8 года 2 мес. назад #102596 от Amphilohiy
Недавно я мучал доктора Бага небольшим скриптецом. Суть была показать как можно в отдельном модуле организовать обменник данными. Просто метод положить и метод достать, все просто. Как обычно я не задумываясь организовал это дело через коллекцию в константе, и в итоге пришлось разъяснять почему так можно, ибо кажется что менять константу нельзя. Посему я решил просто поизголятся и углубиться в эту теорию несколько глубже, хоть тема и кажется тривиальной.
Первое что может сбить с толку это ощущение что переменная и есть объект (перепрочтите заголовок). Но это не совсем так, переменные являются представлением объекта, что то вроде портала через который можно достать объект и помучать его. В этом плане 2 переменных, содержащих один объект, это два портала с одной точкой выхода.
А оператор присвоения (знак '=') это настройка телепорта на объект. Ага, если слева находится именно переменная/константа (а не конструкция посложнее) то объект и не меняется никаким образом. Разница между присвоением и конструкцией посложнее:
Code:
variable = [] # присвоение variable.field = [] # метод field= объекта variable['field'] = [] # метод []= объекта
Пример того как присваивание не меняет объект, но иная конструкция меняет:
Code:
array = [ { value: 'value' } ] array2 = array element = array[0] element = { value: 'not value (obviously!)' } p array # => [{:value=>"value"}] Мы назначили на переменную element новое значение, сам массив не постродал array[0] = { value: 'not value (obviously!)' } p array # => [{:value=>"not value (obviously!)"}] Мы использовали оператор []= который поменял исходный массив array2 = [] p array # => [{:value=>"not value (obviously!)"}] Мы переприсвоили переменную, а не пересоздали объект

Позвольте мозгу поработать над этой идеей, ибо дальше будет продолжение. Так вот - константы. Единственное отличие константы от переменной это невозможность повторного использования оператора присвоения. Мда, это все. Тот же пример с константой:
Code:
ARRAY = [ { value: 'value' } ] array2 = ARRAY element = ARRAY[0] element = { value: 'not value (obviously!)' } p ARRAY # => [{:value=>"value"}] ARRAY[0] = { value: 'not value (obviously!)' } p ARRAY # => [{:value=>"not value (obviously!)"}] array2 = [] p ARRAY # => [{:value=>"not value (obviously!)"}]

То есть разница между первым и вторым только в присвоении, как в следующем примере:
Code:
variable = [] variable = [] CONSTANT = [] CONSTANT = [] # Ошибка

И хоть я и пишу это про руби, те же 2 примера работают так же и в яваскрипте. Переменная:
Code:
var array = [ { value: 'value' } ]; var array2 = array; var element = array[0]; element = { value: 'not value (obviously!)' }; console.log(array); // => [ { value: 'value' } ] Мы назначили на переменную element новое значение, сам массив не постродал array[0] = { value: 'not value (obviously!)' }; console.log(array); // => [ { value: 'not value (obviously!)' } ] Мы использовали оператор []= который поменял исходный массив array2 = []; console.log(array); // => [ { value: 'not value (obviously!)' } ] Мы переприсвоили переменную, а не пересоздали объект

Константа:
Code:
const ARRAY = [ { value: 'value' } ]; var array2 = ARRAY; var element = ARRAY[0]; element = { value: 'not value (obviously!)' }; console.log(ARRAY); // => [ { value: 'value' } ] ARRAY[0] = { value: 'not value (obviously!)' }; console.log(ARRAY); // => [ { value: 'not value (obviously!)' } ] array2 = []; console.log(ARRAY); // => [ { value: 'not value (obviously!)' } ]

Таааак выходит что константа практически не влияет на возможность изменения объекта. В данном случае надо делить типы объектов на изменияемые и неизменяемые. Так например число неизменяемое, а массив изменяемое. Почему? Да потому что для первого не существующет метода, который бы мог изменить сам объект, вместо этого всегда возвращается новый объект в результате вызова. Например в Руби строки изменяемые, а в js нет:
Code:
CONSTANT = 'cat' CONSTANT[0] = 'b' puts CONSTANT # => bat
Code:
const CONSTANT = 'cat'; CONSTANT[0] = 'b'; // Может repl.it голимый, может так и положено, но ошибки небыло console.log(CONSTANT); // 'cat'

И единственный выход в js это создать новую строку на основе той, что хранится в константе. Это, пожалуй, все. Все примеры, как вы уже поняли, я проверял на этом сайте .

Я верю, что иногда компьютер сбоит, и он выдает неожиданные результаты, но остальные 100% случаев это чья-то криворукость.
Спасибо сказали: Dmy, Doctor_Bug

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Проект месяца 3 место 3 место 3 место в Кодировке Программист Ruby Проект месяца 1 место Ветеран
Больше
8 года 2 мес. назад #102598 от Doctor_Bug
Не пойму, как это у тебя все работает? Начинаю детальную диагностику.
ВНИМАНИЕ: Спойлер!

Баг изучает Godot Engine. А слушает эту музыку ~~> Мое сердце

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

3 место Готв Учитель 2 место
Больше
8 года 2 мес. назад - 8 года 2 мес. назад #102599 от EvilCat
Всё это происходит в языках, где массивы являются объектами. Когда я пришла в Яваскрипт из php, то однажды долго искала непонятную ошибку, а оказалось, что при передаче массива как аргумента в функцию передаётся тот же самый массив, а не его копия, и все изменения в него внутри функции отражались и вне функции.

Как это в php (не тот же самый код, что в примере Амфи!)
Code:
$array = [ ['value' => 'value'] ]; $array2 = $array; $array[0] = [ 'value' => 'not value (obviously!)' ]; var_dump($array2); # => array ( 'value' => 'value' ) - потому что массивы не объекты, и присвоив $array2 массив из $array, мы создали его независимую копию.

Есть два способа обойти это, если хочется массивов-объектов.

Во-первых, создать класс, реализующий интерфейс ArrayAccess, тогда его экземпляры будут вести себя как массивы, но будут, конечно, объектами.

Во-вторых, или можно использовать специальное присвоение, с помощью которого две переменных обращаются к одному и тому же адресу.
Code:
$array = [ ['value' => 'value'] ]; $array2 =& $array; $array[0] = [ 'value' => 'not value (obviously!)' ]; var_dump($array2); # => array ( 'value' => 'not value (obviously!)' ) - теперь содержимое переменных меняется одновременно. $array2[0] = [ 'value' => 'another third value' ]; var_dump($array); # => array ( 'value' => 'another third value' ) - теперь содержимое переменных меняется одновременно.

Главное - не перепутать оператор =& (присвоить по тому же адресу) c &= (побитовое перемножение), а также не запутаться, если несколько переменных связаны этим отношением:
Code:
$a = 1; $b = 2; $b =& $a; var_dump($b); # => int(1) $b = 3; var_dump($a); # => int(3) $c = 100; $a =& $c; var_dump($b); # => int(3) var_dump($a); # => int(100)

Протестировать php в онлайне можно здесь , только не забудьте <? в начале, ведь всё-таки php исконно - шаблонизатор.

[hr]

Ещё немного php'шной магии. Связывать адресами можно не только переменные, но и элементы массивов. Например:
ВНИМАНИЕ: Спойлер!
Последнее редактирование: 8 года 2 мес. назад пользователем EvilCat.
Спасибо сказали: Dmy, Amphilohiy, Doctor_Bug

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Программист Ruby Ветеран Даритель Стимкея Оратор Программист JavaScript
Больше
8 года 2 мес. назад #102601 от Lekste
Но и это особо не спасет, если в вашем массиве другой массив, который также скопируется.
Или когда в массиве объекты, которые не будут копироваться вместе с самим массивом и, можно случайно поменять эти объекты внутри своей функции и напакостить тому, кто будет использовать их же вне вашей функции. :)

Так что избегайте функций, которые требуют что-то в качестве аргумента по-ссылке. А если в каком-то случае без этого по-другому никак, убедитесь, что скопировали все, что требуется и не меняете чего-то лишнего.
Но лучше избегайте передачи по-ссылке. Так меньше вероятность ошибки и прозрачней логика.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

3 место Готв Учитель 2 место
Больше
8 года 2 мес. назад #102604 от EvilCat
От всего этого спасёт правильное наименование функций. В Руби это очень удобно, что знак вопроса и восклицательный знак можно использовать в названии функции, поэтому деструктивные функции (не просто возвращающие что-то, а имеющие побочные эффекты) можно помечать им. А в C# (Unity), если не ошибаюсь, для передачи по ссылке нужно и в функции, и в вызове поставить ключевое слово out перед аргументом.
Спасибо сказали: Dmy, Doctor_Bug

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Программист Ruby Ветеран Даритель Стимкея Оратор Программист JavaScript
Больше
8 года 2 мес. назад - 8 года 2 мес. назад #102605 от Lekste
Но, возврат по ссылке же логику направленную в одну сторону - от входов к выходу делает ненаправленной. :(
Да и функции, которые делают 2 и больше действий же нарушают принцип выполнения разделения ответственности.
Хотя как-обычно в особых случаях, вроде упомянутой деструктивной или какого-нибудь save или send. Выглядит нормально.
Однако даже без знака вопроса, можно просто к имени такой функции приписать «try”, например. Вроде trySendUserStatistics(stats, &error). И сразу понятно, что может пройти не успешно.
Хотя часто удобней в таких случаях кидать исключения. С ними и описание функции не забьётся ничем лишним, и ловить можно в удобных местах.

А побочные эффекты пусть создаются в выделенных для них местах, а не где попало, чтоб не лазить и не собирать их потом по коду.
Последнее редактирование: 8 года 2 мес. назад пользователем Lekste.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

3 место Готв Учитель 2 место
Больше
8 года 2 мес. назад #102606 от EvilCat
Ловить исключения удобно, пока не начинаешь писать асинхронные методы.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Программист Ruby Ветеран Даритель Стимкея Оратор Программист JavaScript
Больше
8 года 2 мес. назад - 8 года 2 мес. назад #102608 от Lekste
Но... Вызывающая функция не должна зависеть от результата асинхронного вызова, т.к. Она его не ждёт и отпочковавшийся тред уже сам по-себе и результаты он возвращает через коллбек, что по-идее такое же линейное продолжение выполнения.
Вообщем, если вызвавший поток ждёт результата, то он и вызывает синхронно, если не ждёт, то ему и результат вызова не нужен.
Не понял в чем проблема

Или речь о псевдоассинхронности, когда функция разбита на фрагменты и периодически приостанавливается?
Так я думал там можно спокойно исключения кидать, раз вызывающая функция ждёт результата.
Последнее редактирование: 8 года 2 мес. назад пользователем Lekste.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

3 место Готв Учитель 2 место
Больше
8 года 2 мес. назад #102609 от EvilCat

Она его не ждёт и отпочковавшийся тред уже сам по-себе и результаты он возвращает через коллбек, что по-идее такое же линейное продолжение выполнения.


Линейное-то оно может быть и линейное, но исключения с помощью try...catch уже не половишь.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Программист Ruby Ветеран Даритель Стимкея Оратор Программист JavaScript
Больше
8 года 2 мес. назад #102611 от Lekste
Как не половишь? Коллбек Это же обычная функция. Только ловить надо там, где этот коллбек вызывается

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

3 место Готв Учитель 2 место
Больше
8 года 2 мес. назад - 8 года 2 мес. назад #102612 от EvilCat
Ты знаешь, что если в коллбеке у Promise (в Javascript) возникает исключение, то оно никуда не пробрасывается, а вместо этого Promise разрешается с провалом, и вызывается уже соответствующий провалу коллбек? А если такого коллбека нет, то исключение просто проглатывается, и Promise тихо разрешается провалом.

А если ставить к каждому Promise свой коллбек на провал, то ты попадаешь в ад коллбеков... Это серьёзная проблема, для которой даже придумали специальный сахар , чтобы как-то облегчить. Его даже теоретически уже можно использовать , хотя не факт, что в MV...

P.S. В своём проекте на php я мечтаю перейти на функции-генераторы, которые, когда им нужно что-то асинхронно получить делают
Code:
$result = yield new Need(задача, задача, задача);

В зависимости от того, как создан объект Need, он сам разбирается, считать ли провал одной из задач общим провалом, возвращать ли какие-то значения по умолчанию в случае провала, а то и вообще не возвращаться к этому исполнению... Таким образом, на простейшие случаи не нужно тратить лишние try...catch и делать чтение кода нелинейным.
Последнее редактирование: 8 года 2 мес. назад пользователем EvilCat.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Время создания страницы: 0.109 секунд
Работает на Kunena форум