В компьютерном программировании чистая функция — это функция , которая имеет следующие свойства: [1] [2]
Следующие примеры функций C++ являются чистыми:
floor
, возвращающий нижнюю часть числа;max
, возвращающий максимальное из двух значений.void f () { static std :: atomic < unsigned int > x = 0 ; ++ x ; }
x
может наблюдаться только внутри других вызовов f()
, и поскольку f()
не передает значение x
своему окружению, оно неотличимо от функции void f() {}
, которая ничего не делает. Обратите внимание, что x
это std::atomic
так, чтобы изменения из нескольких потоков, выполняемых f()
одновременно, не приводили к гонке данных , которая имеет неопределенное поведение в C и C++.Следующие функции C++ являются нечистыми, поскольку у них отсутствует указанное выше свойство 1:
int f () { static int x = 0 ; ++ x ; return x ; }
int f () { return x ; }
sin()
не является чистой, поскольку ее результат зависит от режима округления IEEE , который можно изменить во время выполнения.int f ( int * x ) { return * x ; }
int f () { int x = 0 ; std :: cin >> x ; return x ; }
Следующие функции C++ являются нечистыми, поскольку у них отсутствует указанное выше свойство 2:
void f () { static int x = 0 ; ++ x ; }
void f () { ++ x ; }
void f ( int * x ) { ++* x ; }
void f () { std :: cout << "Привет, мир!" << std :: endl ; }
Следующие функции C++ являются нечистыми, поскольку у них отсутствуют оба вышеуказанных свойства 1 и 2:
int f () { static int x = 0 ; ++ x ; return x ; }
int f () { int x = 0 ; std :: cin >> x ; return x ; }
Ввод-вывод изначально нечист: операции ввода подрывают ссылочную прозрачность , а операции вывода создают побочные эффекты. Тем не менее, есть смысл, в котором функция может выполнять ввод или вывод и оставаться чистой, если последовательность операций на соответствующих устройствах ввода-вывода моделируется явно как аргумент и результат, а операции ввода-вывода считаются неудачными, когда последовательность ввода не описывает операции, фактически выполненные с момента начала выполнения программы. [ необходимо разъяснение ]
Второй пункт гарантирует, что единственная последовательность, используемая в качестве аргумента, должна изменяться при каждом действии ввода-вывода; первый пункт позволяет различным вызовам функции, выполняющей ввод-вывод, возвращать различные результаты из-за изменения аргументов последовательности. [3] [4]
Монада ввода-вывода — это идиома программирования, обычно используемая для выполнения ввода-вывода в чистых функциональных языках.
Выходные данные чистой функции могут быть предварительно вычислены и кэшированы в таблице поиска. В технике, называемой мемоизацией , любой результат, возвращаемый заданной функцией, кэшируется, и при следующем вызове функции с теми же входными параметрами возвращается кэшированный результат вместо повторного вычисления функции.
Мемоизацию можно выполнить, обернув функцию в другую функцию ( функцию-обертку ). [5]
С помощью мемоизации можно сократить вычислительные затраты, необходимые для вычисления самой функции, за счет накладных расходов на управление кэшем и увеличения требований к памяти.
Программа на языке C для кэшированного вычисления факториала ( assert()
прерывается с сообщением об ошибке, если ее аргумент ложен; на 32-битной машине значения за пределами этого числа fact(12)
в любом случае не могут быть представлены. [ необходима цитата ]
static int fact ( int n ) { return n <= 1 ? 1 : fact ( n -1 ) * n ; } int fact_wrapper ( int n ) { static int cache [ 13 ]; assert ( 0 <= n && n < 13 ); if ( cache [ n ] == 0 ) cache [ n ] = fact ( n ); return cache [ n ]; }
Функции, которые обладают только указанным выше свойством 2, то есть не имеют побочных эффектов, допускают методы оптимизации компилятора, такие как устранение общих подвыражений и оптимизацию цикла , аналогичную арифметическим операторам. [6] Примером C++ является length
метод, возвращающий размер строки, который зависит от содержимого памяти, на которую указывает строка, поэтому не имеет указанного выше свойства 1. Тем не менее, в однопоточной среде следующий код C++
std :: string s = "Привет, мир!" ; int a [ 10 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 }; int l = 0 ; для ( int i = 0 ; i < 10 ; ++ i ) { l += s . длина () + a [ i ]; }
можно оптимизировать таким образом, чтобы значение s.length()
вычислялось только один раз, перед циклом.
Некоторые языки программирования позволяют объявлять чистое свойство функции:
pure
ключевое слово может использоваться для объявления функции, которая просто не имеет побочных эффектов (т. е. имеет только указанное выше свойство 2). [7] Компилятор может вывести свойство 1 поверх объявления. [8] См. также: Возможности языка Fortran 95 § Чистые процедуры .pure
определяет свойство 2, в то время как const
атрибут определяет действительно чистую функцию с обоими свойствами. [9]constexpr
C++ (оба свойства). [10] См. также: C++11 § constexpr – Обобщенные константные выражения .Поскольку чистые функции имеют идентичные возвращаемые значения для идентичных аргументов , они хорошо подходят для модульного тестирования .