Можно ли сказать, что имеет место следующая дихотомия:
Каждая заданная функция
- либо чистый
- или имеет побочные эффекты
Если это так, побочные эффекты (функции) - это все, что невозможно найти в чистой функции.
Можно ли сказать, что имеет место следующая дихотомия:
Каждая заданная функция
Если это так, побочные эффекты (функции) - это все, что невозможно найти в чистой функции.
Это очень зависит от определений, которые вы выбираете. Определенно справедливо сказать, что функция чиста или нечиста. Чистая функция всегда возвращает тот же результат и не изменяет среду. Нечистая функция может возвращать разные результаты, когда она выполняется повторно (что может быть вызвано тем, что что-то делает для среды).
Являются ли все примеси побочными эффектами? Я бы так не сказал - функция может зависеть от чего-то в среде, в которой она выполняется. Это может быть чтение некоторой конфигурации, местоположения GPS или чтения данных из Интернета. Это не "побочные эффекты", потому что они ничего не делают с миром.
Я думаю, что есть два разных вида примесей:
Выходная примесь - это когда функция делает что-то в мире. В Haskell это моделируется с использованием monads - нечистая функция a -> b
на самом деле является функцией a -> M b
, где M
захватывает другие вещи, которые она делает с миром.
Входная примесь - это когда функция требует что-то из среды. Нечистая функция a -> b
может быть смоделирована как функция C a -> b
, где тип C
захватывает другие объекты из среды, к которой может обращаться функция.
Монад и выходные примеси, безусловно, более известны, но я думаю, что входные примеси одинаково важны. Я написал мою кандидатскую диссертацию о входных примесях, которую я называю коэффициентами, поэтому я мог бы быть предвзятым ответом.
Чтобы функция была чистой, она должна:
Но, вы видите, это определяет функциональную чистоту со свойством или отсутствием побочных эффектов. Вы пытаетесь применить обратную логику, чтобы вычесть определение побочных эффектов с использованием чистых функций, которые логически должны работать, но практически определение побочного эффекта не имеет ничего общего с функциональной чистотой.
Я не вижу проблем с определением чистой функции: чистая функция - это функция. То есть он имеет домен, кодомен и отображает элементы первого в элементы последнего. Он определяется на всех входах. Он ничего не делает для среды, потому что "среда" в этой точке не существует: нет машин, которые могут выполнять (для некоторого определения "выполнить" ) заданную функцию. Существует всего лишь тотальное отображение от чего-то к чему-то.
Затем какой-то капиталист решает вторгнуться в мир четко определенных функций и поработить таких чистых существ, но их справедливая сущность не может выжить в нашей жестокой реальности, функции становятся грязными и начинают нагревать процессор.
Таким образом, среда отвечает за то, что процессор нагревается, и имеет смысл говорить о чистоте до того, как ее владелец подвергся насилию и казни. Точно так же ссылочная прозрачность является свойством языка - она не выполняется в среде вообще, потому что в компиляторе может быть ошибка, или метеорит может упасть на вашу голову, и ваша программа перестанет выдавать тот же результат.
Но есть и другие существа: темные обитатели подземного мира. Они выглядят как функции, но они знают об окружающей среде и могут взаимодействовать с ней: читать переменные, отправлять сообщения и запускать ракеты. Мы называем этих падших родственников функциями "нечистыми" или "эффективными" и избегаем как можно больше, потому что их природа настолько темна, что невозможно рассуждать о них.
Таким образом, существует явная разница между теми функциями, которые могут взаимодействовать с внешней стороной, и теми, которые этого не делают. Однако определение "снаружи" также может меняться. Монада State
моделируется с использованием только чистых инструментов, но мы думаем о f : Int -> State Int Int
как о эффективном вычислении. Более того, non-terminal и исключения (error "..."
) являются эффектами, но haskellers обычно не считают их такими.
Подводя итог, чистая функция представляет собой четко определенную математическую концепцию, но мы обычно рассматриваем функции в языках программирования, а то, что чистое, зависит от вашей точки зрения, поэтому не имеет смысла говорить о дихотомиях, когда они участвуют понятия не являются четко определенными.
Способ определения чистоты функции f
равен ∀x∀y x = y ⇒ f x = f y
, т.е. с учетом того же аргумента функция возвращает тот же результат или сохраняет равенство.
Это не то, что люди обычно имеют в виду, когда говорят о "чистых функциях"; они обычно означают "чистый", поскольку "не имеют побочных эффектов". Я не понял, как квалифицировать "побочный эффект" (комментарии приветствуются!), Поэтому мне не о чем говорить.
Тем не менее, я исследую эту концепцию чистоты, потому что она может предложить некоторую взаимосвязанную проницательность. Я не эксперт здесь; это в основном я просто бессвязно. Однако я надеюсь, что это искромет некоторые проницательные (и корректирующие!) Комментарии.
Чтобы понять чистоту, мы должны знать, о каком равенстве мы говорим. Что означает x = y
, а что означает f x = f y
?
Один выбор - это семантическое равенство Хаскелла. То есть, равенство семантики Haskell присваивает своим членам. Насколько я знаю, для Haskell нет официальной денотационной семантики, но Wikibooks Haskell Denotational Semantics предлагает разумный стандарт, который, я думаю, более или менее соглашается на ad hoc. Когда Haskell говорит, что его функции чисты, это равенство, на которое оно ссылается.
Другой выбор - это определяемое пользователем равенство (т.е. (==)
) путем получения класса Eq
. Это имеет значение при использовании денотационного дизайна - то есть мы назначаем нашу семантику условиям. С помощью этого выбора мы можем случайно записать функции, которые являются нечистыми; Haskell не занимается нашей семантикой.
Я буду ссылаться на семантическое равенство Haskell как =
и определяемое пользователем равенство как ==
. Также я предполагаю, что ==
является отношением равенства - это не выполняется для некоторых экземпляров ==
, например, для Float
.
Когда я использую x == y
в качестве предложения, что я действительно имею в виду, это x == y = True ∨ x == y = ⊥
, потому что x == y :: Bool
и ⊥ :: Bool
. Другими словами, когда я говорю, что x == y
истинно, я имею в виду, что если он вычисляет что-то другое, кроме ⊥, то он вычисляет значение True.
Если x
и y
равны в соответствии с семантикой Хаскелла, то они равны по любой другой семантике, которую мы можем выбрать.
Доказательство: если x = y
, то x == y ≡ x == x
и x == x
истинно, потому что ==
является чистым (согласно =
) и рефлексивным.
Аналогично доказывается ∀f∀x∀y x = y ⇒ f x == f y
. Если x = y
, то f x = f y
(потому что f
чисто), поэтому f x == f y ≡ f x == f x
и f x == f x
истинно, потому что ==
является чистым и рефлексивным.
Вот глупый пример того, как мы можем нарушить чистоту для пользовательского равенства.
data Pair a = Pair a a
instance (Eq a) => Eq (Pair a) where
Pair x _ == Pair y _ = x == y
swap :: Pair a -> Pair a
swap (Pair x y) = Pair y x
Теперь мы имеем:
Pair 0 1 == Pair 0 2
Но:
swap (Pair 0 1) /= swap (Pair 0 2)
Примечание:
¬(Pair 0 1 = Pair 0 2)
, поэтому нам не гарантировано, что наше определение(==)
будет в порядке.
Более убедительным примером является рассмотрение Data.Set
. Если x, y, z :: Set A
, то вы надеетесь, что это будет выполнено, например:
x == y ⇒ (Set.union z) x == (Set.union z) y
Особенно, когда Set.fromList [1,2,3]
и Set.fromList [3,2,1]
обозначают один и тот же набор, но, вероятно, имеют разные (скрытые) представления (не эквивалентные семантике Haskell). То есть мы хотим быть уверены, что ∀z Set.union z
является чистым в соответствии с (==)
для Set
.
Вот тип, с которым я играл:
newtype Spry a = Spry [a]
instance (Eq a) => Eq (Spry a) where
Spry xs == Spry ys = fmap head (group xs) == fmap head (group ys)
A Spry
- это список, который имеет не равные смежные элементы. Примеры:
Spry [] == Spry []
Spry [1,1] == Spry [1]
Spry [1,2,2,2,1,1,2] == Spry [1,2,1,2]
Учитывая это, что представляет собой чистая реализация (согласно ==
для Spry) для flatten :: Spry (Spry a) -> Spry a
такая, что если x
является элементом суб-spry, это также элемент сплющенного spry (то есть что-то как ∀x∀xs∀i x ∈ xs[i] ⇒ x ∈ flatten xs
)? Упражнение для читателя.
Также стоит отметить, что функции, о которых мы говорили, находятся в одном домене, поэтому они имеют тип A → A
. То есть, за исключением тех случаев, когда мы доказали ∀f∀x∀y x = y ⇒ f x == f y
, который пересекает семантический домен Haskell в наш собственный. Это может быть какой-то гомоморфизм... возможно, теоретик категории может весить здесь (и, пожалуйста, сделайте!).
Побочные эффекты являются частью определения языка. В выражении
f e
побочные эффекты e
- это все части поведения e
, которые "вытесняются" и становятся частью поведения выражения приложения, а не передаются в f
как часть значения e
.
Для конкретного примера рассмотрим эту программу:
f x = x; x
f (print 3)
где концептуально синтаксис x; x
означает "запустите x, затем запустите его снова и верните результат".
В языке, где print
записывает в stdout как побочный эффект, это записывает
3
потому что вывод является частью семантики выражения приложения.
В языке, где вывод print
не является побочным эффектом, он пишет
3
3
потому что вывод является частью семантики переменной x
в определении f
.