Мы должны различать значение выражения и его представление в памяти.
Например, оба эти выражения имеют одинаковое значение :
e1 = ("Hello", "Hello")
e2 = let s = "Hello" in (s, s)
Действительно, в Хаскеле нет способа провести различие между результатами оценки приведенных выше выражений. Они семантически эквивалентны.
Реализация на Haskell (например, GHC) может представлять эти значения в памяти любым способом, который не нарушает семантику. Например:
- Может дважды сохранить строку
"Hello"
в памяти, а затем использовать пару указателей (p1, p2)
.
- Он может сохранить строку
"Hello"
один раз в памяти, а затем использовать пару указателей (p, p)
.
Обратите внимание, что теоретически оба представления могут использоваться для любого из выражений e1,e2
выше. На практике GHC будет использовать первое для e2
, а второе для e1
, но это не важно.
На ваших деревьях возникает та же проблема. Значение вашего a6
- это дерево. GHC, вероятно, представляет это дерево как DAG, не являющийся деревом (то есть DAG, который, если преобразован в неориентированный граф, имеет цикл), но это не имеет значения, это всего лишь деталь реализации. Важным аспектом является то, что такое представление учитывает семантику дерева.
Тогда можно задаться вопросом, почему представление DAG является правильным, если значение является деревом. Это верно, потому что в Haskell мы не можем сравнивать базовые «ссылки» p
, используемые GHC. Если бы у нас была функция comparePtr :: a -> a -> Bool
, которая сравнивает такие ссылки, мы могли бы различать e1
и e2
, используя comparePtr (fst e) (snd e)
для e
между e1
, e2
. Это ужасно нарушило бы правильность реализации. В Хаскеле, однако, этого нет.
(Технически, есть функция unsafe
, которая делает это, но функции unsafe
никогда не должны использоваться в "нормальном" коде. Мы обычно притворяемся, что они не существуют.)