Ссылочная прозрачность, относящаяся к функции, означает, что вы можете определить результат применения этой функции, только взглянув на значения ее аргументов. Вы можете написать ссылочно прозрачные функции на любом языке программирования, например, Python, Scheme, Pascal, C.
С другой стороны, в большинстве языков вы также можете писать нереферентно прозрачные функции. Например, эта функция Python:
counter = 0
def foo(x):
global counter
counter += 1
return x + counter
не является ссылочно прозрачным, фактически вызывает
foo(x) + foo(x)
а также
2 * foo(x)
будет выдавать разные значения для любого аргумента x
. Причина этого в том, что функция использует и изменяет глобальную переменную, поэтому результат каждого вызова зависит от этого изменяющегося состояния, а не только от аргумента функции.
Haskell, чисто функциональный язык, строго отделяет оценку выражений, в которой применяются чистые функции и которая всегда прозрачна по ссылкам, от выполнения действия (обработки специальных значений), которое не является ссылочно прозрачным, то есть выполнение того же действия может иметь каждый раз, когда другой результат.
Итак, для любой функции Haskell
f :: Int -> Int
и любое целое число x
, это всегда верно, что
2 * (f x) == (f x) + (f x)
Примером действия является результат библиотечной функции getLine
:
getLine :: IO String
В результате вычисления выражения эта функция (фактически константа) в первую очередь выдает чистое значение типа IO String
. Значения этого типа являются значениями, как и любые другие: вы можете передавать их, помещать в структуры данных, составлять их с помощью специальных функций и так далее. Например, вы можете составить список действий следующим образом:
[getLine, getLine] :: [IO String]
Действия особенные в том, что вы можете указать среде выполнения Haskell выполнить их, написав:
main = <some action>
В этом случае, когда ваша программа на Haskell запущена, среда выполнения проходит через действие, связанное с ним, main
и выполняет его, возможно, вызывая побочные эффекты. Следовательно, выполнение действия не является прозрачным по ссылкам, поскольку выполнение одного и того же действия два раза может привести к различным результатам в зависимости от того, что среда выполнения получает в качестве входных данных.
Благодаря системе типов Haskell действие никогда не может использоваться в контексте, где ожидается другой тип, и наоборот. Итак, если вы хотите найти длину строки, вы можете использовать length
функцию:
length "Hello"
вернет 5. Но если вы хотите найти длину строки, считываемой из терминала, вы не можете написать
length (getLine)
потому что вы получаете ошибку типа: length
ожидается ввод списка типов (а String - это действительно список), но getLine
это значение типа IO String
(действие). Таким образом, система типов гарантирует, что значение действия наподобие getLine
(чье выполнение выполняется за пределами основного языка и которое может быть нереферентно прозрачным) не может быть скрыто внутри значения типа без действия Int
.
РЕДАКТИРОВАТЬ
Чтобы ответить на вопрос exizt, вот небольшая программа на Haskell, которая читает строку из консоли и печатает ее длину.
main :: IO () -- The main program is an action of type IO ()
main = do
line <- getLine
putStrLn (show (length line))
Основное действие состоит из двух подзадач, которые выполняются последовательно:
getline
типа IO String
,
- второе строится путем оценки функции
putStrLn
типа String -> IO ()
по ее аргументу.
Точнее, второе действие построено
- привязка
line
к значению, прочитанному первым действием,
- вычисление чистых функций
length
(вычислить длину как целое число) и затем show
(превратить целое число в строку),
- построение действия путем применения функции
putStrLn
к результату show
.
На этом этапе второе действие может быть выполнено. Если вы ввели «Hello», он напечатает «5».
Обратите внимание, что если вы получаете значение из действия, используя <-
нотацию, вы можете использовать это значение только внутри другого действия, например, вы не можете написать:
main = do
line <- getLine
show (length line) -- Error:
-- Expected type: IO ()
-- Actual type: String
потому что show (length line)
имеет тип, String
тогда как запись do требует, чтобы за действием ( getLine
типа IO String
) следовало другое действие (например, putStrLn (show (length line))
типа IO ()
).
РЕДАКТИРОВАТЬ 2
Определение реляционной прозрачности Йоргом В. Миттагом является более общим, чем мое (я одобрил его ответ). Я использовал ограниченное определение, потому что пример в вопросе фокусируется на возвращаемом значении функций, и я хотел проиллюстрировать этот аспект. Однако RT в целом относится к смыслу всей программы, включая изменения глобального состояния и взаимодействия с окружающей средой (IO), вызванные оценкой выражения. Итак, для правильного общего определения вы должны обратиться к этому ответу.