Ну, если первый случай использовал a
вместо x
следующим образом, то есть, по крайней мере, вероятность того, что GHC устранит выделение нового узла посредством устранения общего подвыражения.
treeInsert x (Node a left right)
| x == a = Node a left right
Однако, все это не имеет значение, но в любом нетривиальном случае использования, потому что путь вниз по дереву к узлу будет дублироваться, даже если элемент уже существует. И этот путь будет значительно длиннее одного узла, если ваш случай использования тривиален.
В мире ML, довольно идиоматический способ избежать этого - выбросить исключение KeyAlreadyExists
, а затем поймать это исключение в функции вставки верхнего уровня и вернуть исходное дерево. Это приведет к тому, что стек будет размотан, вместо того, чтобы выделять любую из Node
s в кучу.
Прямая реализация идиомы ML в принципе не имеет значения в Haskell по уважительным причинам. Если избегать этого дублирования, самое простое и, возможно, самое лучшее, что нужно сделать, это проверить, содержит ли дерево ключ, прежде чем вставлять его.
Недостаток этого подхода, по сравнению с прямой вставкой Haskell или идиомой ML, заключается в том, что он включает в себя два обхода пути вместо одного. Теперь, вот, не дублируя, однопроходные вставки можно реализовать в Haskell:
treeInsert :: Ord a => a -> Tree a -> Tree a
treeInsert x original_tree = result_tree
where
(result_tree, new_tree) = loop x original_tree
loop x EmptyTree = (new_tree, singleton x)
loop x (Node a left right) =
case compare x a of
LT -> let (res, new_left) = loop x left
in (res, Node a new_left right)
EQ -> (original_tree, error "unreachable")
GT -> let (res, new_right) = loop x right
in (res, Node a left new_right)
Однако старые версии GHC (примерно 7-10 лет назад) не справиться с таким родом рекурсии через ленивый пар результатов очень эффективно, и, по моему опыту, проверка перед вставкой, скорее всего, будет лучше. Я был бы немного удивлен, если бы это наблюдение действительно изменилось в контексте более поздних версий GHC.
Конечно, можно представить себе функцию, которая напрямую конструирует (но не возвращает) новый путь для дерева и решает вернуть новый путь или исходный путь, как только будет известно, существует ли этот элемент уже. (Новый путь немедленно станет мусором, если он не будет возвращен.) Это соответствует основным принципам среды выполнения GHC, но на самом языке не выражается.
Конечно, любая полностью не дублирующая функция вставки в ленивой структуре данных будет иметь разные свойства строгости, чем простая дублирующая вставка. Поэтому, независимо от техники реализации, они отличаются друг от друга, если речь идет о лени.
Но, конечно, независимо от того, дублируется ли путь, это может не иметь значения. Случаи, когда это было бы наиболее важно, - это когда вы используете дерево настойчиво, потому что в случаях линейного использования старый путь становится мусором сразу после каждой вставки. И, конечно, это имеет значение только при вставке значительного количества дубликатов.
Хороший вопрос. Моя немедленная реакция - «что говорит Ядро?» – MathematicalOrchid
Просто для лучшей производительности я бы сопоставлял совпадение на 'compare x a', чтобы избежать двойного (или даже трехкратного) сравнения/равенства. –
@MathematicsOrchid, Спасибо за ваш быстрый ответ. У вас есть какая-либо ссылка, где я мог бы найти больше информации, когда вы упомянули Core? – FtheBuilder