2015-02-21 3 views
6

Я думал в том, как реализовать эквивалент unfold для следующего типа:Какое правильное определение `unfold` для немаркированного дерева?

data Tree a = Node (Tree a) (Tree a) | Leaf a | Nil 

Это было не сразу очевидно, так как стандарт unfold для списков возвращает значение и следующее семя. Для этого типа данных это не имеет смысла, поскольку нет значения, пока вы не достигнете листового узла. Таким образом, действительно имеет смысл возвращать новые семена или останавливаться со значением. Я использую это определение:

data Drive s a = Stop | Unit a | Branch s s deriving Show 

unfold :: (t -> Drive t a) -> t -> Tree a 
unfold fn x = case fn x of 
    Branch a b -> Node (unfold fn a) (unfold fn b) 
    Unit a  -> Leaf a 
    Stop  -> Nil 

main = print $ unfold go 5 where 
    go 0 = Stop 
    go 1 = Unit 1 
    go n = Branch (n - 1) (n - 2) 

Хотя это, похоже, работает, я не уверен, что так оно и должно быть. Итак, вот в чем вопрос: какой правильный способ это сделать?

+1

Ум ... это похоже на единственную нормальную вещь, которую вы можете сделать. – dfeuer

+1

Действительно? Я был уверен, что здесь делаю что-то глупое, но если это правильно, я могу просто удалить вопрос. – MaiaVictor

+0

Вопрос нетривиален, и он может быть полезен другим. – chi

ответ

7

Если вы считаете, что тип данных является фиксированной точкой функтора, то вы можете видеть, что ваше определение является разумным обобщением списка.

module Unfold where 

Здесь мы начинаем по определению неподвижной опоры функтора f: это слой f затем еще немного фиксированной точкой:

newtype Fix f = InFix { outFix :: f (Fix f) } 

Чтобы сделать вещи немного яснее, вот определение функторов соответствующих спискам и деревьям. Они имеют в основном ту же форму, что и типы данных, за исключением того, что мы заменили рекурсивные вызовы дополнительным параметром. Другими словами, они описывают, что один слой из списка/дерева выглядит и является общим по возможным подструктурам r.

data ListF a r = LNil | LCons a r 
data TreeF a r = TNil | TLeaf a | TBranch r r 

Списки и деревья, то соответственно закрепление для предоставления ListF и TreeF:

type List a = Fix (ListF a) 
type Tree a = Fix (TreeF a) 

В любом случае, прыгая вы теперь лучше интуиция об этом Fixpoint бизнесе, мы можем видеть, что есть общий способ определения функции unfold.

Учитывая изначальное семя, а также функции, принимая семя и строительство один слой из f где рекурсивной структуры являются новые семена, мы можем построить всю структуру:

unfoldFix :: Functor f => (s -> f s) -> s -> Fix f 
unfoldFix node = go 
    where go = InFix . fmap go . node 

Это определение специализируется на обычный unfold в списке или ваше определение для деревьев. Другими словами: ваше определение действительно было правильным.

+1

Спасибо за объяснение причины алгоритма, очень интересно видеть, как связаны мои типы данных «Диск» и «Дерево», и насколько кратким может быть общий разворот. Интересно, почему это не так, как это определено в Prelude? – MaiaVictor

+2

Определение всех ваших рекурсивных типов данных с помощью Fix делает совпадение шаблонов довольно уродливым, а также имеет (возможно, невыносимую) стоимость исполнения. – user2407038

+1

@ user2407038 С этой целью было бы очень приятно, если бы у нас был экземпляр 'Coercible [a] (Fix (ListF a)), позволяющий нам перемещаться между равными рекурсивными типами и их братом с фиксированной точкой. Это будет иметь нулевую стоимость исполнения и разрешить сопоставление шаблонов для обоих типов.(Я предполагаю, что они имеют идентичное представление времени исполнения, так как 'Fix' является новым типом - я мог бы ошибаться в этом, хотя). – chi

Смежные вопросы