Как ленивая оценка позволяет большую модульность?

В своей статье " Почему функциональное программирование имеет значение" Джон Хьюз утверждает, что "Ленивые вычисления, пожалуй, самый мощный инструмент модульности в репертуаре функционального программиста". Для этого он приводит такой пример:

Предположим, у вас есть две функции: "infiniteLoop" и "terminationCondition". Вы можете сделать следующее:

terminationCondition(infiniteLoop input)

Ленивая оценка, по словам Хьюза, "позволяет отделить условия завершения от тел цикла". Это определенно верно, так как "TerminationCondition" с использованием ленивых вычислений здесь означает, что это условие может быть определено вне цикла - infiniteLoop прекратит выполнение, когда terminationCondition перестанет запрашивать данные.

Но разве функции высшего порядка не могут достичь того же, что и ниже?

infiniteLoop(input, terminationCondition)

Как ленивая оценка обеспечивает модульность здесь, которая не обеспечивается функциями высшего порядка?

1 ответ

Решение

Да, вы можете использовать пройденный в проверке завершения, но для этого работает автор infiniteLoop пришлось бы предусмотреть возможность прекращения цикла с такого рода условиями и жестко связать вызов с условием завершения с их функцией.

И даже если конкретное условие может быть передано как функция, его "форма" предопределена автором infiniteLoop, Что если они дадут мне условие завершения "слот", которое вызывается для каждого элемента, но мне нужен доступ к последним нескольким элементам, чтобы проверить какое-то условие сходимости? Возможно, для простого генератора последовательностей вы могли бы придумать "наиболее общий возможный" тип условия завершения, но не очевидно, как это сделать, и при этом оставаться эффективным и простым в использовании. Должен ли я многократно передавать всю последовательность так далеко в условие завершения, если это то, что он проверяет? Вынуждаю ли я вызывающих абонентов свернуть их простые условия завершения в более сложный пакет, чтобы они соответствовали наиболее общему типу условий?

Вызывающие, безусловно, должны точно знать, как вызывается условие завершения, чтобы предоставить правильное условие. Это может быть немного зависим от этой конкретной реализации. Если они переключаются на другую реализацию infiniteLoop написано другой третьей стороной, насколько вероятно, что будет использоваться точно такой же дизайн для условия завершения? С ленивым infiniteLoopЯ могу отказаться от любой реализации, которая должна производить ту же последовательность.

А что если infiniteLoop не простой генератор последовательности, но на самом деле генерирует более сложную бесконечную структуру данных, как дерево? Если все ветви дерева генерируются независимо рекурсивно (представьте себе дерево ходов для такой игры, как шахматы), может иметь смысл разрезать разные ветви на разной глубине, основываясь на всевозможных условиях для информации, сгенерированной к настоящему времени.

Если первоначальный автор не подготовился (либо специально для моего варианта использования, либо для достаточно общего класса вариантов использования), мне не повезло. Автор ленивый infiniteLoop можете просто написать это естественным путем и позволить каждому отдельному звонящему лениво исследовать, что они хотят; ни один не должен знать много о другом вообще.

Кроме того, что, если решение прекратить лениво исследовать бесконечный вывод фактически перемежается (и зависит от) вычислений, которые делает вызывающая сторона с этим выводом? Подумайте о дереве шахматных ходов снова; насколько далеко я хочу исследовать одну ветвь дерева, может легко зависеть от моей оценки лучшего варианта, который я нашел в других ветвях дерева. Так что либо я делаю свой обход и вычисление дважды (один раз в условии завершения, чтобы вернуть флаг, говорящий infinteLoop чтобы остановить, а затем еще раз с конечным выводом, чтобы я мог на самом деле иметь свой результат), или автор infiniteLoop Я должен был подготовиться не только к условию завершения, но и к сложной функции, которая также возвращает результат (чтобы я мог поместить все мои вычисления в "условие завершения").

В крайнем случае, я мог изучить результаты и рассчитать некоторые результаты, отобразить их для пользователя и получить входные данные, а затем продолжить изучение структуры данных (не вспоминая infiniteLoop на основе ввода пользователя). Оригинальный автор ленивый infiniteLoop Мне не нужно знать, что я когда-нибудь подумал бы о том, чтобы сделать такую ​​вещь, и она все равно будет работать. Если мы добьемся чистоты, обеспечиваемой системой типов, то это будет невозможно при подходе с условием переданного завершения, если только infiniteLoop было разрешено иметь побочные эффекты, если необходимо условие завершения (скажем, давая все это монадический интерфейс).

Короче говоря, чтобы обеспечить ту же гибкость, которую вы получили бы при ленивой оценке с использованием строгого infiniteLoop для управления которыми требуются функции более высокого порядка, это может быть очень сложным как для автора infiniteLoop и его вызывающий объект (если не открыто множество более простых оболочек, и один из них не соответствует сценарию использования вызывающего). Ленивая оценка может позволить производителям и потребителям быть почти полностью отделенными, в то же время давая потребителю возможность контролировать, сколько продукции производит производитель. Все, что вы можете сделать таким образом, вы можете сделать с дополнительными аргументами функции, как вы говорите, но это требует от производителя и потребителя по существу согласовать протокол о том, как работают функции управления; и этот протокол почти всегда либо специализирован для рассматриваемого варианта использования (связывая потребителя и производителя вместе), либо настолько сложен, чтобы быть полностью общим, чтобы производитель и потребитель были привязаны к этому протоколу, который вряд ли будет воссоздан заново. в другом месте, и поэтому они все еще связаны вместе.

Другие вопросы по тегам