Точечный свободный стиль, необходимый для оптимизированного Карри
Скажем, у нас есть (надуманная) функция, такая как:
import Data.List (sort)
contrived :: Ord a => [a] -> [a] -> [a]
contrived a b = (sort a) ++ b
И мы частично применяем его для использования в других местах, например:
map (contrived [3,2,1]) [[4],[5],[6]]
На поверхности это работает так, как можно было бы ожидать:
[[1,2,3,4],[1,2,3,5],[1,2,3,6]]
Однако, если мы бросим некоторые traces в:
import Debug.Trace (trace)
contrived :: Ord a => [a] -> [a] -> [a]
contrived a b = (trace "sorted" $ sort a) ++ b
map (contrived $ trace "a value" [3,2,1]) [[4],[5],[6]]
Мы видим, что первый список, переданный в contrived, оценивается только один раз, но сортируется для каждого элемента в [4,5,6]:
[sorted
a value
[1,2,3,4],sorted
[1,2,3,5],sorted
[1,2,3,6]]
Теперь, contrived можно довольно просто перевести в свободный от точек стиль:
contrived :: Ord a => [a] -> [a] -> [a]
contrived a = (++) (sort a)
Который когда частично применяется:
map (contrived [3,2,1]) [4,5,6]
Все еще работает так, как мы ожидаем:
[[1,2,3,4],[1,2,3,5],[1,2,3,6]]
Но если мы снова добавим traces:
contrived :: Ord a => [a] -> [a] -> [a]
contrived a = (++) (trace "sorted" $ sort a)
map (contrived $ trace "a value" [3,2,1]) [[4],[5],[6]]
Мы видим, что теперь первый список, переданный в contrived, оценивается и сортируется только один раз:
[sorted
a value
[1,2,3,4],[1,2,3,5],[1,2,3,6]]
Почему это так? Поскольку перевод в безынерционный стиль настолько тривиален, почему GHC не может вывести, что ему нужно только один раз отсортировать a в первой версии contrived?
Примечание: я знаю, что для этого довольно тривиально например, вероятно, предпочтительнее использовать стиль pointfree. Это надуманный пример, который я немного упростил. Реальная функция, с которой у меня возникает проблема, менее ясна (на мой взгляд), когда выражена в стиле pointfree:
realFunction a b = conditionOne && conditionTwo
where conditionOne = map (something a) b
conditionTwo = somethingElse a b
В стиле без точек это требует написания уродливой оболочки (both) вокруг (&&):
realFunction a = both conditionOne conditionTwo
where conditionOne = map (something a)
conditionTwo = somethingElse a
both f g x = (f x) && (g x)
В стороне, я также не уверен, почему работает обертка both; pointfree стиль realFunction ведет себя как версия pointfree стиля contrived в том, что частичное приложение оценивается только один раз (т. е. если something отсортировать a, то это будет сделано только один раз). По-видимому, поскольку both не является беспредметным, у Хаскелла должна быть та же проблема, что и с беспредметным contrived.
1 ответ:
Если я правильно понял, вы ищете вот это:
Если вы хотите, чтобы сортировка была вычислена только один раз, это должно быть сделано доcontrived :: Ord a => [a] -> [a] -> [a] contrived a = let a' = sort a in \b -> a' ++ b -- or ... in (a' ++)\b.Вы правы в том, что компилятор может оптимизировать это. Это называется" полная лень " оптимизации.
Если я правильно помню, GHC не всегда делает это, потому что это не всегда реальная оптимизация, в общем случае. Рассмотрим надуманный пример
foo :: Int -> Int -> Int foo x y = let a = [1..x] in length a + yПри передаче обоих аргументов, приведенный выше код работает в постоянном пространстве: элементы списка сразу же отбрасываются по мере их создания.
При частичном применении
xзакрытие дляfoo xтребует только O (1) памяти, так как список еще не сформирован. Код типаПо-прежнему работают в постоянном пространстве.let f = foo 1000 in f 10 + f 20 -- (*)Вместо этого, если бы мы написали
foo :: Int -> Int -> Int foo x = let a = [1..x] in (length a +)Тогда
(*)больше не будет работать в постоянном пространстве. Первый вызовf 10выделит список длиной 1000 и сохранит его в памяти для второго вызова. вызовf 20.
Обратите внимание, что ваше частичное приложение
... = (++) (sort a)По существу означает
... = let a' = sort a in \b -> a' ++ bПоскольку передача аргумента включает привязку, как в
let. Таким образом, результат вашегоsort aсохраняется для всех будущих вызовов.