Я видел много примеров в функциональных языках о обработке списка и создании функции для чего-то с ее элементами после получения некоторого дополнительного значения (обычно не присутствующего во время создания функции), например:
-
Вычисление разницы между каждым элементом и средним значением
(последние два примера в разделе "ЛАЗИВАЯ оценка" )
-
Постановка списка добавляется в строгие функциональные языки, такие как ML/OCaml, чтобы избежать перебора первого списка более одного раза
(раздел под названием "Staging" )
-
Сравнение списка с другим с помощью foldr (т.е. создание функции для сравнения другого списка с первым)
listEq a b = foldr comb null a b where comb x frec [] = False comb x frec (e:es) = x == e && frec es cmp1To10 = listEq [1..10]
Во всех этих примерах авторы обычно замечают преимущество перебора исходного списка только один раз. Но я не могу удержаться от мысли "уверен, вместо того, чтобы пересекать список из N элементов, вы проходите цепочку из N оценок, так что?". Я знаю, что в этом есть какая-то польза, может кто-нибудь объяснить это, пожалуйста?
Изменить: Спасибо за ответы. К сожалению, это не то, что я хотел знать. Я попытаюсь уточнить свой вопрос, поэтому он не путается с (более распространенным) о создании промежуточных списков (о которых я уже читал в разных местах). Также спасибо за исправление моего форматирования сообщений.
Мне интересны случаи, когда вы создаете функцию, которая будет применяться к списку, где у вас еще нет необходимого значения для оценки результата (будь то список или нет). Тогда вы не можете избежать генерации ссылок на каждый элемент списка (даже если структура списка больше не указана). И у вас есть тот же доступ к памяти, что и раньше, но вам не нужно деконструировать список (сопоставление образцов).
Например, см. главу "постановка" в упомянутой книге ML. Я пробовал это в ML и Racket, точнее, в поэтапной версии "append", которая пересекает первый список и возвращает функцию, чтобы вставить второй список в хвост, не пересекая первый список много раз. Удивительно для меня, это было намного быстрее, даже если ему все же пришлось копировать структуру списка, поскольку последний указатель был другим в каждом случае.
Ниже представлен вариант карты, который после применения к списку должен быть быстрее при изменении функции. Поскольку Haskell не является строгим, мне пришлось бы заставить оценивать listMap [1..100000]
в cachedList
(или, может быть, нет, поскольку после первого приложения он все равно должен быть в памяти).
listMap = foldr comb (const [])
where comb x rest = \f -> f x : rest f
cachedList = listMap [1..100000]
doubles = cachedList (2*)
squares = cachedList (\x -> x*x)
-- print doubles and squares
-- ...
Я знаю, что в Haskell это не имеет значения (пожалуйста, исправьте меня, если я ошибаюсь), используя comb x rest f = ...
vs comb x rest = \f -> ...
, но я выбрал эту версию, чтобы подчеркнуть эту идею.
Обновление: после некоторых простых тестов, я не мог найти разницы во времени выполнения в Haskell. Тогда возникает вопрос только о строгих языках, таких как Scheme (по крайней мере, реализация Racket, где я его тестировал) и ML.