Y комбинатор и оптимизация хвостовых вызовов

20

Определение Y комбинатора в F #

let rec y f x = f (y f) x

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

let y f x = f (y f) x = f (f (y f)) x = f (f (f (y f))) x etc...

Проблема заключается в том, что априори эта схема исключает использование какой-либо оптимизации хвостового вызова: действительно, в f может быть некоторая ожидающая операция, и в этом случае мы не можем просто изменить локальный кадр стека, связанный с f.

Так :

  • с одной стороны, использование Y комбинатора требует явно другого продолжения, чем сама функция.
  • Что касается применения TCO, мы хотели бы, чтобы в f не было ожидающих операций, а вызывался только сам f.

Знаете ли вы, каким образом эти двое могли бы примириться? Как Y с трюком аккумулятора или Y с трюком CPS? Или аргумент, доказывающий, что это невозможно сделать?

никола
источник
Вы добавили функцию rec в свою реализацию? Я должен думать, что это нужно из моего чтения ..
Джимми Хоффа
У вас есть доказательства, что это не оптимизирует хвостовой вызов? Я должен подумать, что вы, возможно, захотите прочитать IL для этой функции и увидите, я не удивлюсь, если компилятор достаточно умен, чтобы что-то придумать ...
Джимми Хоффа
в случае прямой несвязанной рекурсии это не так: однако вы можете переписать ее, чтобы учесть такую ​​вещь, учитывая тот факт, что кадр стека повторно используется через вызов y. да, возможно, нужно увидеть IL, нет опыта в этом.
Николас
5
Я сделал аккаунт и получил 50 баллов просто за комментарий здесь. Этот вопрос действительно интересный. Я думаю, что это полностью зависит от f. Мы можем видеть, что это yможет привести к ответному звонку fс громоотводом (y f), но, как вы говорите, fможет иметь некоторую незавершенную операцию. Я думаю, что было бы интересно узнать, есть ли отдельный комбинатор, который более дружествен к разговору. Интересно, получит ли этот вопрос больше внимания на сайте CS Stackexchange?
Джон Картрайт

Ответы:

4

Знаете ли вы, каким образом эти двое могли бы примириться?

Нет, и не без причины, ИМХО.

Y-комбинатор является теоретической конструкцией и необходим только для того, чтобы завершить вычисление лямбда-исчисления (помните, что в лямбда-исчислении нет циклов, и у лямбд нет имен, которые мы могли бы использовать для рекурсии).

Таким образом, комбинатор Y поистине увлекателен.

Но : Никто на самом деле не использует Y-комбинатор для реальной рекурсии! (За исключением, может быть, для развлечения, чтобы показать, что это действительно работает.)

Оптимизация хвостового вызова, OTOH, как следует из названия, является оптимизацией. Это ничего не добавляет к выразительности языка, мы заботимся только о практических соображениях, таких как пространство стека и производительность рекурсивного кода.

Итак, ваш вопрос звучит так: Есть ли аппаратная поддержка для снижения бета-версии? (Бета-сокращение - это то, как сокращаются лямбда-выражения, вы знаете.) Но ни один функциональный язык (насколько мне известно) не компилирует свой исходный код в представление лямбда-выражений, которое будет сокращено бета-версиями во время выполнения.

Инго
источник
2
Y-комбинатор походит на развязывание узла, который продолжает развязываться после каждого использования. Большинство систем сокращают это и связывают узел на метауровне так, что его никогда не нужно удалять.
Дэн Д.
1
Что касается последнего абзаца, рассмотрим Haskell, который в своей основе использует сокращение графов для выполнения ленивых вычислений. Но мой фаворит - оптимальное сокращение, которое всегда идет по пути решетки Черча-Россера с наименьшим сокращением до полной нормальной формы. Например, в «Оптимальной реализации языков функционального программирования» Асперти и Геррини . Также см. BOHM 1.1 .
Дэн Д.
@DanD. Спасибо за ссылки, я попробую их позже в браузере с поддержкой PostScript. Конечно, есть чему поучиться для меня. Но вы уверены, что скомпилированный haskell сокращает график? Я сомневаюсь в этом.
Инго
1
На самом деле он использует сокращение графов: «GHC компилируется в G-машину без тегов без меток (STG). Это условная машина сокращения графов (то есть виртуальная машина, которая выполняет сокращение графов, как описано выше)». От ... Подробнее о STG-машине см. Simon Peyton Jones: « Реализация ленивых функциональных языков на стандартном оборудовании: G-машина без меток Spineless» .
Дэн Д.
@DanD. в той же статье, которую вы связали, далее говорится, что GHC «проводит ряд оптимизаций в этом представлении, прежде чем окончательно скомпилировать его в реальный машинный код (возможно, через C с использованием GCC)».
Инго
0

Я не совсем уверен в этом ответе, но это лучшее, что я мог придумать.

Y комбинатор по своей природе ленив, в строгих языках лень должна добавляться вручную через дополнительные лямбды.

let rec y f x = f (y f) x

Ваше определение выглядит так, как будто оно требует лени для завершения, иначе (y f)аргумент никогда не завершит оценку и должен будет оценить, fиспользовал ли он его или нет . TOC в ленивом контексте является более сложным, и, кроме того, результатом (y f)является повторное составление функции без применения с x. Я не уверен, что это нужно для O (n) памяти, где n - глубина рекурсии, но я сомневаюсь, что вы могли бы достичь того же типа TOC, что возможно с чем-то вроде (переключение на Haskell, потому что я на самом деле не знаю F #)

length acc []    = acc
length acc (a:b) = length (acc+1) b 

Если вы еще не знаете об этом, разница между Хаскелом foldlи foldl'в Хаскеле может пролить свет на ситуацию. foldlнаписано как было бы сделано на нетерпеливом языке. Но вместо того, чтобы быть TOC'd, это на самом деле хуже, чем foldrпотому, что аккумулятор хранит потенциально огромный гром, который не может быть частично оценен. (Это связано с тем, что как foldl, так и foldl 'не работают с бесконечными списками.) Таким образом, в более поздних версиях Haskell foldl'был добавлен, который заставляет вычислять аккумулятор каждый раз, когда функция повторяется, чтобы гарантировать, что не создается огромный поток. Я уверен, что http://www.haskell.org/haskellwiki/Foldr_Foldl_Foldl%27 может объяснить это лучше, чем я.

Ericson2314
источник