Является ли эта функция JavaScript, принимающая изменяемый ссылочный аргумент, чистой функцией?

У меня тот же вопрос, что и у этого, но в контексте JavaScript.

Из Википедии:

возвращаемое значение [чистой функции] одинаково для одинаковых аргументов

Там также утверждается, что чистая функция не может иметь изменение возвращаемого значения с изменяемыми ссылочными аргументами. В JavaScript каждый нормальный объект передается как изменяемый аргумент ссылки. Рассмотрим следующий пример:

const f = (arr) => arr.length

const x = []
console.log( f(x) ) // 0
x.push(1);
console.log( f(x) ) // 1

Является ли приведенное выше доказательством того, что f нечисто? Или вы возразите, что мы не вызываем f с одним и тем же аргументом в обоих случаях?

Я вижу, как имеет смысл вызывать f impure в языке/среде, где другие потоки потенциально могут испортить изменяемый аргумент ссылки во время выполнения f. Но так как f не async, это невозможно. x останется неизменным с момента вызова f до завершения его выполнения. (Если я правильно понимаю, эта интерпретация, по-видимому, поддерживается определением того же самого, приведенным в § 4.1 документа Поддающаяся проверке функциональная чистота в Java.)

Или я что-то упускаю? Есть ли пример в JavaScript, где функция, не содержащая асинхронного кода, просто теряет свойство ссылочной прозрачности? потому что он принимает изменяемую ссылку, но было бы чисто, если бы мы использовали, например. вместо этого структура данных Immutable.js?


person mb21    schedule 24.01.2021    source источник
comment
Говорится ли в определении «тот же объект» или «то же самое значение»…? Является ли список одним и тем же значением в обоих случаях? Что, если вы сериализовали и несериализовали его? Что, если заменить аргумент литералом вместо переменной?   -  person deceze♦    schedule 24.01.2021
comment
В том-то и дело, что в определении не сказано, что они имеют в виду под словом «то же самое». Судя по вашим вопросам, вы, похоже, согласны со мной, что одна и та же сериализация будет иметь больше смысла, чем одна и та же ссылка на объект?   -  person mb21    schedule 24.01.2021
comment
В Javascript объекты получают идентичность через свои ссылки. Это свойство ссылочной идентичности противоречит ссылочной прозрачности в чисто функциональных языках. Так что формально в Javascript нет чистых функций, но можно добиться желаемого поведения по соглашению и уверенности.   -  person Iven Marquardt    schedule 24.01.2021
comment
Если вы придерживаетесь соглашения о чистоте, то можете быть вполне уверены, что f — это чистая функция.   -  person Iven Marquardt    schedule 24.01.2021
comment
@scriptum формально, в Javascript нет чистых функций - это рассуждение наверняка относится и к Java? Но вы проверили документ, на который я ссылался?   -  person mb21    schedule 24.01.2021
comment
Я читаю газету. Интересно читать (+1)   -  person trincot    schedule 24.01.2021
comment
по крайней мере, в js могут быть функции, которые являются чистыми (среди тех, которые принимают в качестве аргументов замороженные объекты, примитивные типы)   -  person grodzi    schedule 24.01.2021
comment
Из статьи: На эти вопросы нет ни одного заведомо правильного ответа. Таким образом, детерминизм является параметризованным свойством: данное определение того, что означает эквивалентность аргументов, метод является детерминированным, если все вызовы с эквивалентными аргументами возвращают результаты, неотличимые от внутри языка.   -  person trincot    schedule 24.01.2021
comment
@trincot да, но определение эквивалента, которое они дали в § 4.1, похоже, говорит о том, что мы не вызываем f с эквивалентными аргументами в двух случаях в моем примере.   -  person mb21    schedule 24.01.2021
comment
Одной из причин выделения чистых функций является удаление ненужных вызовов к ним или изменение порядка кода. Вы должны были бы считать два x разными, чтобы функция считалась чистой. Если это то, что делает анализатор, прекрасно. Но без этого контекста эта функция не является чистой.   -  person ikegami    schedule 24.01.2021
comment
Так это не вопрос мнения? В самой статье предполагается, что определение эквивалентности — это выбор, который необходимо сделать (в определенных границах). Так ты просишь о выборе? Я думаю, это приведет к самоуверенным ответам.   -  person trincot    schedule 24.01.2021
comment
Разработчики Redux считают f = () => [] чистым, даже если f() === f() ложно, потому что [] === [] ложно. Так что вам обязательно нужно определить, что означает эквивалентность вещей в контексте того, как вы собираетесь их использовать. Также подумайте, что произойдет, если вы просто запечете мутацию в качестве получателя свойства: x = { get length() { return global++; } } Тогда f(x) == f(x) может легко оказаться ложным, если заранее установить global в 1. Так что arr => arr.length так же чист, как g => g()   -  person Wyck    schedule 24.01.2021
comment
Если мы возьмем определение того же самого из статьи, которое, кажется, сводится к чему-то вроде JSON.stringify(a) === JSON.stringify(b) (оставляя в стороне такие случаи, как NaN и т. д.), и предположим, что никто не вмешивался в какие-либо прототипы и т. д., то использование изменяемого ссылочного аргумента кажется идеальным безопасно — если вы не используете асинхронный код — верно?   -  person mb21    schedule 24.01.2021
comment
Или, возможно, lodash _.isEqual является еще более полезным определением того же самого на практике.   -  person mb21    schedule 24.01.2021


Ответы (1)


При буквальном рассмотрении определения Википедии функция, которая принимает в качестве аргумента ссылку на изменяемые данные структура (например, собственный массив) не является чистой:

Его возвращаемое значение одинаково для одних и тех же аргументов (никаких изменений с локальными статическими переменными, нелокальными переменными, изменяемыми ссылочными аргументами или входными потоками от устройств ввода-вывода).

Эквивалентность

Хотя это ясно говорит об отсутствии изменений с изменяемыми ссылочными аргументами, мы могли бы сказать, что это открыто для интерпретации и зависит от значения слов same и variation. Возможны разные определения, и поэтому мы вступаем в область мнений. Цитата из статьи, на которую вы ссылаетесь:

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

Функциональная чистота, предложенная в той же статье, использует следующее определение эквивалентности:

Два набора ссылок на объекты считаются эквивалентными, если они приводят к идентичным графам объектов.

Таким образом, с этим определением следующие два массива считаются эквивалентными:

let a = [1];
let b = [1];

Но эту концепцию нельзя применить к JavaScript без дополнительных ограничений. Ни к Java, по которой авторы статьи ссылаются на урезанный язык, названный Joe-E:

объекты имеют идентичность: концептуально у них есть «адрес», и мы можем сравнить, указывают ли две ссылки на один и тот же «адрес» с помощью оператора ==. Это понятие идентичности объекта может разоблачить недетерминизм.

Проиллюстрировано на JavaScript:

const compare = (array1, array2) => array1 === array2;

let arr = [1];
let a = compare(arr, arr);
let b = compare(arr, [1]);
console.log(a === b); // false

Поскольку два вызова возвращают разные результаты, даже если аргументы имеют одинаковую форму и содержание, мы должны заключить (с этим определением эквивалентности), что приведенная выше функция compare не является чистой. В то время как в Java вы можете влиять на поведение оператора == (Joe-E запрещает вызывать Object.hashCode) и таким образом избегать этого, в JavaScript это обычно невозможно при сравнении объектов.

Непреднамеренные побочные эффекты

Другая проблема заключается в том, что JavaScript не является строго типизированным, и поэтому функция не может быть уверена, что аргументы, которые она получает, соответствуют их назначению. Например, следующая функция выглядит чистой:

const add = (a, b) => a + b;

Но это может быть вызвано тем, что дает побочные эффекты:

const add = (a, b) => a + b;

let i = 0;
let obj = { valueOf() { return i++ } };
let a = add(1, obj);
let b = add(1, obj);
console.log(a === b); // false

Та же проблема существует с функцией в вашем вопросе:

const f = (arr) => arr.length;

const x = { get length() { return Math.random() } };
let a = f(x);
let b = f(x);
console.log(a === b) // false

В обоих случаях функция непреднамеренно вызвала нечистую функцию и вернула результат, который от нее зависел. Хотя в первом примере легко сделать функцию чистой с помощью проверки typeof, для вашей функции это менее тривиально. Мы можем думать о instanceof или Array.isArray, или даже о какой-то умной функции deepCompare, но тем не менее вызывающие могут установить прототип странного объекта, установить его свойство конструктора, заменить примитивные свойства на геттеры, обернуть объект в прокси, ... и т.д., и т.д. , и так одурачить даже самых умных контролеров на равенство.

Прагматизм

Поскольку в JavaScript слишком много незавершенных вопросов, нужно быть прагматичным, чтобы иметь полезное определение чистоты, иначе почти ничто не может быть помечено как чистое.

Например, на практике многие вызовут функцию типа Array#slice чистой, даже несмотря на то, что она страдает от упомянутых выше проблем (в том числе связанных со специальным аргументом this).

Вывод

В JavaScript при вызове функции в чистом виде вам часто приходится согласовывать контракт о том, как должна вызываться функция. Аргументы должны быть определенного типа и не иметь (скрытых) методов, которые можно было бы вызвать, но которые являются нечистыми.

Кто-то может возразить, что это противоречит идее pure, которая должна только определяться самим определением функции, а не тем, как она в конечном итоге может быть вызвана.

person trincot    schedule 24.01.2021
comment
Спасибо! Хорошая демонстрация с функцией compare. Я думаю, на практике я был бы в порядке, называя это нечистым... - person mb21; 25.01.2021
comment
В статье Википедии, которую вы цитировали, уже отмечается: Некоторые авторы, особенно из сообщества императивных языков, используют термин "чистый" для всех функций, которые имеют указанное выше свойство 2, что все еще весьма полезно. Итак, пока вы не измените изменяемый аргумент (и никто другой этого не сделает), ваша функция останется чистой. - person Bergi; 25.01.2021
comment
Также была добавлена ​​часть, которая ссылается на изменяемые ссылочные аргументы в этих правках и не подкреплен цитатой из грамотного. На странице обсуждения также есть несколько обсуждений. - person Bergi; 25.01.2021