Перевод статьи Джастина Этеридж (Justin Etheredge), в которой автор объясняет тонкую разницу между детерминированными и чистыми функциями.
Вчера я читал блог Мэтта Подвизоки (Matt Podwysocki) (этот блог, кстати, потрясающий, идите и подпишитесь), и у него есть пост «Recursing into Recursion – Memoization». Отличный пост, если вы хотите познакомиться с мемоизацией. У меня уже был пост об обобщенной функции мемоизации некоторое время назад, поэтому мы будем говорить не о мемоизации. То, что возбудило во мне интерес, было в конце статьи Мэтта:
Моя первая мысль была «Эй, Мэтт, чистота подразумевает, что функция не имеет побочных эффектов, а детерминированность означает, что функция всегда возвращает одни и те же результаты для данных наборов параметров». Затем, как у любого хорошего программиста, моей второй мыслью было «На самом деле, возможно, я не прав». Поэтому я пошел и изучил этот вопрос, и не удивительно, что я ошибался.
Вот описание чистой функции из википедии:
А вот определение детерминированного алгоритма Национальным Институтом стандартов и технологий (США):
Поэтому чистые функции на самом деле являются подмножеством детерминированных функций. Все чистые функции являются детерминированными, но не все детерминированные функции являются чистыми. Например, мы могли бы иметь функцию, которая принимает определенный набор параметров и возвращает детерминированный результат и еще записывает значения на диск или выводит на консоль. По определению такая функция не будет чистой, хоть и будет детерминированной.
Например:
Детерминированная и чистая:
Детерминированная, но не чистая:
Детерминированные функции так же не могут использовать внешнее состояние, потому что оно повлияет на их функционирование. Большинство определений детерминированных функций говорит, что вы можете определить результат по входным данным.
Как Мэтт отметил в своем посте, для мемоизации очень важно, чтобы функция была детерминированной. Это очевидно: из-за того, что мы имеем множество входных данных и затем мы кешируем множество результатов, мы надеемся на то, что функция будет возвращать те же результаты после каждого вызова с этими входными данными, иначе своей мемоизацией мы поменяем поведение функции.
Чистота функций важна по многим причинам, но в первую очередь, потому что она позволяет нам легче рассуждать о поведении функции. Если функция чистая и не имеет побочных эффектов, то мы можем с большей уверенностью рассуждать о производительности этой функции, поскольку нам нужно будет рассматривать меньше переменных. Так же вы не можете эффективно заставить функцию с побочными эффектами выполняться параллельно без сложного анализа её поведения. Чистая функция не должна зависеть от чего бы то ни было, кроме значений переданных ей. Такая функция не меняет никакого разделенного состояния и поэтому может выполняться параллельно без использования блокировок.
Если вы не знали до этого, что представляют собой чистые функции, надеюсь теперь у вас есть хорошее представление. Так же я надеюсь, что если у вас были какие-то заблуждения на их счет, как у меня, теперь всё прояснилось.
Действительно здорово узнать что-то новое, но еще лучше узнать о том, что ваши знания были неверны.
Вчера я читал блог Мэтта Подвизоки (Matt Podwysocki) (этот блог, кстати, потрясающий, идите и подпишитесь), и у него есть пост «Recursing into Recursion – Memoization». Отличный пост, если вы хотите познакомиться с мемоизацией. У меня уже был пост об обобщенной функции мемоизации некоторое время назад, поэтому мы будем говорить не о мемоизации. То, что возбудило во мне интерес, было в конце статьи Мэтта:
Не забывайте, что корректная мемоизация требует, чтобы функция, которую вы пишете, была чистой. Это означает, что для одних и тех же входных данных будут вычисляться и возвращаться одни и те же результаты.
Моя первая мысль была «Эй, Мэтт, чистота подразумевает, что функция не имеет побочных эффектов, а детерминированность означает, что функция всегда возвращает одни и те же результаты для данных наборов параметров». Затем, как у любого хорошего программиста, моей второй мыслью было «На самом деле, возможно, я не прав». Поэтому я пошел и изучил этот вопрос, и не удивительно, что я ошибался.
Вот описание чистой функции из википедии:
- Функция всегда вычисляет одинаковое результирующее значение для одних и тех же наборов аргументов. Результирующее значение функции не может зависеть от любой скрытой информации или состояния, которое может изменяться по мере выполнения программы, не может зависеть от любых внешних входных данных от устройств ввода/вывода.
- Вычисление результата не должно вызывать любые семантически наблюдаемые побочные эффекты, такие как мутацию изменяемых объектов, или вывод в устройства ввода/вывода.
А вот определение детерминированного алгоритма Национальным Институтом стандартов и технологий (США):
Алгоритм, поведение которого можно полностью предсказать по входным данным.
Поэтому чистые функции на самом деле являются подмножеством детерминированных функций. Все чистые функции являются детерминированными, но не все детерминированные функции являются чистыми. Например, мы могли бы иметь функцию, которая принимает определенный набор параметров и возвращает детерминированный результат и еще записывает значения на диск или выводит на консоль. По определению такая функция не будет чистой, хоть и будет детерминированной.
Например:
Детерминированная и чистая:
public int Add(int val1, int val2)
{
return val1 + val2;
}
Детерминированная, но не чистая:
public int Add(int val1, int val2)
{
Console.WriteLine(val1 + " " + val2);
return val1 + val2;
}
Детерминированные функции так же не могут использовать внешнее состояние, потому что оно повлияет на их функционирование. Большинство определений детерминированных функций говорит, что вы можете определить результат по входным данным.
Как Мэтт отметил в своем посте, для мемоизации очень важно, чтобы функция была детерминированной. Это очевидно: из-за того, что мы имеем множество входных данных и затем мы кешируем множество результатов, мы надеемся на то, что функция будет возвращать те же результаты после каждого вызова с этими входными данными, иначе своей мемоизацией мы поменяем поведение функции.
Чистота функций важна по многим причинам, но в первую очередь, потому что она позволяет нам легче рассуждать о поведении функции. Если функция чистая и не имеет побочных эффектов, то мы можем с большей уверенностью рассуждать о производительности этой функции, поскольку нам нужно будет рассматривать меньше переменных. Так же вы не можете эффективно заставить функцию с побочными эффектами выполняться параллельно без сложного анализа её поведения. Чистая функция не должна зависеть от чего бы то ни было, кроме значений переданных ей. Такая функция не меняет никакого разделенного состояния и поэтому может выполняться параллельно без использования блокировок.
Если вы не знали до этого, что представляют собой чистые функции, надеюсь теперь у вас есть хорошее представление. Так же я надеюсь, что если у вас были какие-то заблуждения на их счет, как у меня, теперь всё прояснилось.
Действительно здорово узнать что-то новое, но еще лучше узнать о том, что ваши знания были неверны.