Вот почему ленивые вычисления - хорошая вещь. Они также полезны и на локальных уровнях программы. Так, если Haskell-программист будет писать определение функции вместе с локальными определениями, то сделает это так: “f от x равно тому-то и тому-то там, где. ..”, и после инструкции “там, где” - сколько-то определений, нужных далеко не во всех случаях. Но все равно их следует перечислить. Те, которые нужны, будут вычислены, те, которые не нужны, - не будут. И программист думает: “О черт, все эти подвыражения надо вычислить, но вычислить нельзя, потому что все полетит из-за деления на ноль. Поставлю-ка я определение в правую ветвь условия”.
А в нашем случае - ничего подобного. Надо просто написать те вспомогательные определения, которые могут понадобиться, и те, что понадобятся, будут вычислены. Это очень, очень удобный инструмент.
Но вернемся к общим положениям. С ленивым механизмом вычисления сложнее предсказать, когда понадобится вычислить выражение. И если вы хотите вывести что-нибудь на экран, то язык с вызовом по значению, где порядок вычисления явно определен, делает это при помощи “функции” с побочным эффектом - я специально ставлю кавычки, так как это вовсе не функция, - с типом, скажем, string -> unit. При вызове функции она печатает что-то на экране - в виде побочного эффекта. Это есть в Лисп, в ML, в любом языке с вызовом по значению.
А в чистом языке, если есть функция string -> unit, ее никогда не надо вызывать: вы ведь знаете, что она выдаст всего лишь ответ типа unit. Больше она не делает ничего. А каков ответ, вы знаете. Но поскольку функция с побочными эффектами, очень важно вызвать ее. В ленивом языке проблема вот какая: вы говорите “f применяется к print 'привет”, а вычисляет ли f свой первый аргумент, для вас неясно. Это все происходит внутри функции. Если же аргументов будет два — print 'привет' и print пока', - она может выполнить один, или оба в любом порядке, или ни одного. Поэтому при ленивых вычислениях делать ввод /вывод при помощи побочных эффектов невозможно. Вы не можете написать таким способом рациональную, надежную, предсказуемую программу. Сначала это было непривычно - нет, собственно, никакого ввода/вывода. И потому долгое время в программах использовалась только функция string -> string. Целая программа делала только это. Строка на входе была вводом, а строка результата - выводом. Вот и все.
Можно было слегка усложнить схему, закодировав в строке вывода команды вывода, которые выполнялись внешним интерпретатором. Строка вывода могла скомандовать: “Вывести вот это на экран, а вон то сохранить на диске”. Интерпретатор уже умел это делать. Итак, мы имели замечательную, чисто функциональную программу, а нехороший интерпретатор интерпретировал командную строку. Но если считываешь файл, как вернуть ввод в программу? Просто: сделать строку с командами вывода, которые интерпретируются нехорошим интерпретатором, и произвести ленивое вычисление - результат поступает обратно на вход. Итак, теперь программа преобразует поток ответов в поток запросов. Поток запросов направляется на нехороший интерпретатор, каждый запрос генерирует ответ, который идет затем на вход. Поскольку вычисление ленивое, программа выдает ответ с таким расчетом, чтобы он успел пройти цикл и прийти на вход. Правда, это работало не без сбоев - если ответ поглощался слишком агрессивно, программа зависала, ведь нужен был ответ на вопрос, которого еще не было на выходе.
Смысл в том, что ленивость загнала нас в угол, и пришлось решать вопрос с вводом/выводом. Это была самая важная проблема ленивого программирования. Но начиналось-то все с другого - с того, как это прикольно, как здорово.
Сейбел:За все то время, что вы занимаетесь программированием, как изменились ваши представления о нем как таковом?
Пейтон-Джонс:Думаю, больше всего это связано с монадами и системами типизации. В начале 1980-х я думал лишь о чисто функциональном программировании с относительно простой системой типизации. Теперь же я думаю о нем как о сочетании функционального, императивного и параллельного программирования, причем связь между ними идет через монады. Типы стали гораздо сложнее, позволяя создавать намного более широкий спектр программ, чем я предусматривал когда-то. Можно считать это эволюцией моих взглядов на программирование.
Сейбел:После первой неудачной попытки вы написали не один компилятор. Наверняка вы поняли, в чем секрет создания успешного компилятора.
Читать дальше
Конец ознакомительного отрывка
Купить книгу