2016-06-16 8 views
2

Это вариант на «Compute an (infinite) tree from fixpoint operator using delay modality».Вычислить бесконечное дерево из корневых путей с использованием метода задержки

Установка. Изучается язык бинарных деревьев, дополненной с возможностью ссылаться на произвольные другие узлы в дереве на пути от корня:

type Path = [Bool] 
data STree = SNode STree STree 
      | SPath Path 
      | SLeaf Int 
    deriving (Show) 

Путь определен в контексте какой-то корень, и определяет поддерева, найденного последовательно следующими слева/справа детьми, когда мы видим False/True в пути. Например, путь [False, True] идентифицирует SLeaf 2 в дереве SNode (SNode (SLeaf 1) (SLeaf 2)) (SLeaf 3).

Такие деревья могут быть использованы, например, для идентификации произвольных графов потоков (в том числе неприводимых графиков, то, что не представляется возможным с помощью Fixpoint операторов.)

Мы можем рассматривать бесконечное дерево, вызванное этим описанием.

data Tree = Node Tree Tree 
      | Leaf Int 
    deriving (Show) 

Вот функция преобразования от одного к другому, используя вспомогательную функцию follow, которая находит поддерево в какой-то путь дерева:

follow :: Path -> Tree -> Tree 
follow [] s = s 
follow (False : p) (Node s _) = follow p s 
follow (True : p) (Node _ s) = follow p s 
follow _ (Leaf _) = error "bad path" 

flatten' :: Tree -> STree -> Tree 
flatten' root (SPath p) = follow p root 
flatten' root (SNode s1 s2) = 
    Node (flatten' root s1) (flatten' root s2) 
flatten' _ (SLeaf i) = Leaf i 

flatten :: STree -> Tree 
flatten s = fix (\root -> flatten' root s) 

К сожалению, эта функция не является продуктивным: оно бесконечные петли на flatten (SPath []).

Проблема. Теперь рассмотрим вариант этой структуры, дополненный модами задержки (как описано в «Compute an (infinite) tree from fixpoint operator using delay modality»), а также конструктор Loop для обозначения самореферентных петель.

data Tree = Node (D Tree) (D Tree) 
      | Leaf Int 
      | Loop 
    deriving (Show) 

Без использования без структурно рекурсивных вызовов функций (так, вы можете структурно рекурсию на STree и Path), написать функцию STree -> Tree (или аналогичный), который развертывается бесконечное дерево.

Постскриптум. Во многих случаях мы не хотим вычислять бесконечное разворачивание: мы хотим, чтобы конечное разворачивание, если оно существует, и ошибка в противном случае. В частности, учитывая исходный тип данных:

data Tree = Node Tree Tree 
      | Leaf Int 
    deriving (Show) 

Без использования без структурно рекурсивным, мы могли бы написать функцию STree -> Maybe Tree (или аналогичные), который разворачивает синтаксис в конечное дерево, если оно существует, и не иначе. Отсутствие механизма задержки в этой структуре гарантирует, что оно конечно.

Поскольку в этой структуре нет экземпляров модальности задержки, представляется невозможным сделать это с помощью fixD: мы получим отложенный результат, который мы никогда не сможем использовать. Казалось бы, мы должны преобразовать дерево в граф, топологически отсортировать его, а затем непосредственно реализовать алгоритм ala Gaussian exc без использования fixD.

Есть ли альтернативная типизирующая дисциплина, которая позволяет нам реализовать развертывания так же элегантно, как и в оригинальном (неправильном) коде? Если это так, вы можете просто (повторно) обнаружить другой алгоритм поиска циклов в графах.

+0

Нет ничего, что он мог бы сделать, кроме бесконечного цикла (или возврата некоторого другого нижнего значения) на 'flatten (SPath [])'. Не существует «Дерева», которое могло бы соответствовать этому. В вашем втором примере это может быть «Loop», но для этого требуется обнаружение цикла. Вы должны быть в состоянии либо явно представлять ссылки в дереве (который имел бы известный конструктор), либо знать, какой конструктор путь будет идти априори. – Cirdec

+0

Ну, не так сложно написать функцию завершения, которая использует 'Loop', но, как вы говорите, для этого требуется обнаружение цикла. Может ли очевидная очевидность очевидная производительность этой функции? Вот в чем вопрос! (Я также укажу, что в предыдущем случае был простой способ обнаружения циклов. Менее очевидно здесь!) –

ответ

1

Ну, вот, по крайней мере, некоторые частичные замечания по проблеме.

Конкретная формулировка проблемы, которую я записал, возможно, слишком сложна; труднее, чем предполагалось. Вот один особенно неприятный пример:

SNode (SVar [True, False]) (SVar []) 

Это не хорошо сформированный график, но цикл только становится очевидным после раскладывания SVar [] возникновения. Замените False на True и он будет хорошо сформирован.

Целью было показать неприводимые графики, и на самом деле существует более простой синтаксис, основанный на letrec, который может достичь этой цели. Мы непосредственно принять бесконечное двоичное представление дерева из «функционального программирования с Structured графами» Oliveira-Куком (https://www.cs.utexas.edu/~wcook/Drafts/2012/graphs.pdf) бумагами (без PHOAS и конструкторов переименованное для последовательности):

data STree 
    = SVar Var 
    | SMu [Var] [STree] -- first tree gets "returned" 
    | SLeaf Int 
    | SNode STree STree 

SMu является letrec стиля связывания операции : SMu ["x", "y"] [t1, t2] - по существу letrec x = t1; y = t2 in x. Вместо того, чтобы записывать путь к нужному узлу, вы просто даете ему имя. Кроме того, это делает эту проблему намного более похожей на предыдущий вопрос StackOverflow. (Compute an (infinite) tree from fixpoint operator using delay modality) Итак, можем ли мы решить его таким же образом?

Ответ: «Да, однако ...». Оговорка встречается в этом выражении: SMu ["x", "y"] [SVar "y", SLeaf 0]. Эта «рекурсивная» привязка вообще не рекурсивна, но она будет по-прежнему отвергаться алгоритмом замедленного контекста, поскольку переменная y недоступна в то время, когда мы хотим ее использовать (она не охраняется). Фактически это точно соответствует ограничению, предложенному в «Запрет пустых циклов»: вставленные вхождения f служат в качестве проверки на безопасность, чтобы гарантировать, что циклы не могут произойти.

Очевидно, что все привязки в SMu сильно связаны; Таким образом, наш алгоритм применим только в том случае, если мы сначала вычислим сильно связанные компоненты привязок, предварительно обработав их в по-настоящему рекурсивные привязки (поэтому мы не ошибаемся отклонять нерекурсивные привязки.) Нерекурсивные привязки могут обрабатываться в порядке, не превышающем fixD. В действительности, это соответствует тому, как привязки в, например, Haskell, обрабатываются на практике: мы сначала разделяем привязки на сильно связанные компоненты, а затем обрабатываем SCC по одному, привязывая узел в этих случаях (и ошибки при обнаружении пустого цикл в ГТК.)

Но это еще не все. Рассмотрим SMu ["x" "y"] [SVar "y", SNode (SVar "x") (SVar "x")]. Все компоненты сильно подключены, y не охраняется, и все же нет неохраняемых петель. Таким образом, этого недостаточно, чтобы просто использовать topsort: мы также должны удалить bare-переменные. К счастью, это довольно тривиально (замените все вхождения «x» на «y» в этом случае.)

Итак, что это значит о первоначальной проблеме? Я не думаю, что это полностью решено: укоренившиеся пути затрудняют говорить, что должна быть в первую очередь «топологически отсортированная» версия дерева!