metaclass: (Default)
metaclass ([personal profile] metaclass) wrote2010-09-13 05:45 pm
Entry tags:

Принудительная хвостовая рекурсия: жрать память вместо стека?

Читаю про катаморфизмы и FSharp. Вообще, изначально это я искал ответ на этот вопрос, но зачитался и забыл.

Там приводится пример, как выразить foldBack (оно же foldr) через fold(оно же foldl) с помощью continuations. И выглядит это как будто мы заменяем использование стека в случае не-tail-recursive функции таким же использованием памяти из head. Причем в случае стека выделение и удаление памяти достаточно дешевое, а вот что будет в случае обычной памяти - вопрос загадочный. Непонятно, есть ли тогда какие-нибудь преимущества у такого преобразования не-tail-recursive функции в tail-recursive?

[identity profile] dsorokin.blogspot.com (from livejournal.com) 2010-09-14 09:19 am (UTC)(link)
Иногда все же есть смысл, потому что .NET поддерживает TCO. Только есть нюансы, когда такая оптимизация не работает.

[identity profile] antilamer.livejournal.com 2010-09-14 09:27 am (UTC)(link)
То-то и оно, что вообще никакого смысла нет. При применении полученного continuation'а все равно стек будет наворачиваться.

[identity profile] dsorokin.blogspot.com (from livejournal.com) 2010-09-14 12:03 pm (UTC)(link)
Не совсем понял. В данном примере из той статьи смысла нет или, вообще, стек наворачивается всегда?

Кстати, в исходниках Async есть забавный комментарий (там продолжения используются):

"F# don't always take tailcalls to functions returning 'unit' because this is represented as type 'void' in the underlying IL. Hence we don't use the 'unit' return type here, and instead invent our own type."

[identity profile] antilamer.livejournal.com 2010-09-14 12:05 pm (UTC)(link)
Я не вглядывался в эту конкретную статью, но я помню другую статью, где тоже foldr выражался через foldl или наоборот.
Там вместо того, чтобы сделать нормальный foldr, с помощью foldl строилось замыкание, которое потом сделает foldr.
Так вот, это замыкание для своего *выполнения* требует такого же линейного объема *стека*, а не кучи (а само по себе еще и занимает место в куче).

[identity profile] dsorokin.blogspot.com (from livejournal.com) 2010-09-14 01:02 pm (UTC)(link)
Ну, бывает. Тут засады возможны.

Посмотрел еще раз пример FoldBack. Вроде бы, выглядит логично (но куда проще было бы взять и переписать все через надежный Async :). Только unit как возвращаемый тип несколько беспокоит меня. Если я правильно понял тот комментарий разработчиков F#, то такое продолжение как в статье может запросто съесть и стек на некоторых версиях .NET, а может и сработать как надо.

[identity profile] dsorokin.blogspot.com (from livejournal.com) 2010-09-15 04:21 am (UTC)(link)
Кстати, туплю... Стек не должен съедаться. Функция FoldBack полиморфна, а потому тип unit будет заменен компилятором на внутренний тип Unit. Тогда значение () просто станет null. То есть, злобного void'а не будет. Условия для TOC будут благоприятными.