Почему лень хорошо сочетается с ссылочной прозрачностью?

Я читал учебник по Haskell (Learn You a Haskell), в котором автор сказал, что лень хорошо сочетается с ссылочной прозрачностью. После дополнительного чтения и некоторого поиска я до сих пор не понимаю, почему. Обратите внимание, что я понимаю, что хорошего в ссылочной прозрачности и лени, но именно они вместе беспокоят меня.

Есть ли какая-то особая польза от их комбинации?

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


person vuzun    schedule 05.10.2010    source источник
comment
Я бы выбрал другую формулировку: чтобы предотвратить неприятные сюрпризы, лучше всего, если ленивые выражения будут ссылочно прозрачными. Это скорее необходимость, чем приятность.   -  person Ashkan Kh. Nazary    schedule 14.04.2020


Ответы (3)


Это действительно легко. Нестрогая (например, ленивая) оценка означает, что задачи можно откладывать. Но для того, чтобы что-то отложить, лучше быть уверенным, что позже вы получите тот же результат, что и сейчас, и это ссылочная прозрачность. Рассмотрим этот императивный код Java:

long start = System.currentTimeMillis(); //get the start time
runBenchmarkFunction();
System.out.println("Run took " + (System.currentTimeMillis() - start) + " ms"); 

Теперь ленивый язык отложил бы вычисление первой строки, потому что start нужен только в третьей строке. Таким образом, результат будет 0 (или очень близко к нему). Наверное, это не то, что вы хотите. Причиной этой проблемы может быть то, что System.currentTimeMillis не ссылочно прозрачен. В этом случае у вас не было бы никаких проблем, если бы это была функция в «математическом смысле», такая как sin или ln, которые прозрачны по ссылкам.

person Landei    schedule 05.10.2010
comment
состояние требуется только в третьей строке. Это не совсем так. Побочный эффект запроса системы о текущем времени необходим для запуска второй строки; это тип IO a, а не a. runBenchmarkFunction имеет схожий тип, и чтобы запустить обе, вы должны объединить их с некоторой функцией, которая гарантирует, что действия выполняются по порядку, >> в Haskell. Итак, вы действительно пишете currentTime >>= \st -> runBenchmarkFunction >> currentTime >>= \end -> putStrLn "it took " ++ (show $ end - st) ++ " seconds". Ясно, что вторая строка зависит от первой, так что все работает. - person jrockway; 07.10.2010
comment
@jrockway: я использую здесь вымышленный язык, который вы могли бы назвать ленивой Java, в качестве очень грубого и простого примера для объяснения связи между ленью и ссылочной прозрачностью. Как проблема решается в Haskell — это совершенно другой вопрос. Обратите внимание, что способ монады Haskell - не единственная возможность, например. Clean использует свою систему типов для решения этой проблемы. - person Landei; 07.10.2010
comment
Вопрос помечен как haskell. Но все же то, что вы написали, совершенно референциально прозрачно, если вы представляете правильную семантику. А, если представить другую семантику, то ее нет. Но ключевое слово там - представить. - person jrockway; 08.10.2010

Ссылочная прозрачность означает, что функция всегда будет возвращать один и тот же результат при одних и тех же входных данных. Так что если не имеет значения, является ли функция ленивой или строгой. Ленивая функция будет вычислять свой вывод в неизвестное время в будущем, но из-за ссылочной прозрачности вам гарантируется, что вывод всегда будет одинаковым для заданных входных данных.

Таким образом, ссылочная прозрачность обеспечивает корректность ленивых функций.

person Abhinav Sarkar    schedule 05.10.2010

Рассмотрим этот код Python, где генератор используется для ленивого вычисления бесконечной последовательности. Он не имеет ссылочной прозрачности из-за использования глобального состояния, поэтому вызывающий генератор не может быть уверен, что на получаемый им результат не повлияло какое-либо другое событие.

foo = 0

def foo_sequence():
    global foo
    while True:
        foo += 1
        yield foo

>>> generator = foo_sequence()
>>> generator.next()
1
>>> generator.next()
2
>>> foo = 5
>>> generator.next()
6

В этом случае вызывающая сторона предпочла бы генерировать всю последовательность атомарно, чтобы предотвратить возникновение таких событий. Следовательно, отсутствие ссылочной прозрачности (в этом надуманном примере) делает лень непривлекательной.

person Ben James    schedule 05.10.2010
comment
Очень хороший пример. Конечно, ссылочную прозрачность можно нарушить и в Haskell, хотя обычно на этом пути есть предупреждающие знаки. - person John L; 05.10.2010
comment
@John: Вы имеете в виду использование примитивных вещей или unsafePerformIO? Они считаются вредными, если не используются должным образом. Не могу представить другого пути. - person fuz; 06.10.2010
comment
@FUZxxl, это именно то, что я имею в виду, и префикс unsafe будет предупреждением. Другой способ — обернуть указатель в ByteString, а затем изменить указатель. - person John L; 06.10.2010
comment
@John: Обратите внимание, что unsafePerformIO (и все остальное с unsafe в названии) не является частью стандарта Haskell, поэтому вы не можете на самом деле нарушить ссылочную прозрачность в чистом Haskell (не то, чтобы это было практически актуально). - person sepp2k; 06.10.2010