Учитывая дерево, определенное как:

data Tree a = Leaf | Node (Tree a) a (Tree a) deriving (Eq, Show)

Я хочу использовать функцию:

foldTree :: (b -> a -> b -> b) -> b -> Tree a -> b
foldTree _ b Leaf         = b
foldTree f b (Node lt x rt) = f (foldTree f b lt) x (foldTree f b rt)

Чтобы иметь возможность создавать эквиваленты обычных foldr и foldl как:

foldTreeR :: (a -> b -> b) -> b -> Tree a -> b
foldTreeL :: (b -> a -> b) -> b -> Tree a -> b

Я думал, что это будет довольно просто, поскольку их определения в значительной степени имитируют определения foldr и foldl. Я предполагал, что все, что мне нужно сделать, это вставить значения аналогичным образом, поэтому я напишу анонимную функцию, аккумулятор с базовым состоянием моего дерева и дерево, которое необходимо обработать. Лямбда-функция должна варьироваться в зависимости от типа выполняемого сворачивания.

Вот что я придумал:

foldTreeR :: (a -> b -> b) -> b -> Tree a -> b
foldTreeR f acc t =  foldTree (\x acc -> f x acc) acc t

Я получаю ошибку:

Couldn't match type ‘a’ with ‘a -> b’ 
      ‘a’ is a rigid type variable bound by
        the type signature for:
          foldTreeR :: forall a b. (a -> b -> b) -> b -> Tree a -> b
        at Folds.hs:294:14
      Expected type: Tree (a -> b)
        Actual type: Tree a

Я не совсем уверен, как мне перейти в исходное дерево в этом случае.

Кажется, что левая складка будет просто вариацией того же самого со значениями в лямбда-функции, переупорядоченными, а также оцененными по-разному.

Может ли кто-нибудь помочь мне понять, как здесь можно найти решение?

4
starter 22 Сен 2018 в 01:54

2 ответа

Лучший ответ

Мы можем восстановить линейные свертки, передаваемые через аккумулятор, из древовидных сверток, следующих за типом данных, путем сворачивания в эндофункции , например:

data Tree a = Leaf | Node (Tree a) a (Tree a) deriving (Eq, Show)

-- foldTree :: (b -> a -> b -> b) -> b -> Tree a -> b

foldTreeR :: (a -> r -> r) -> r -> Tree a -> r
foldTreeR cons z t = foldTree g id t z           -- b ~ r -> r
  where
  g lt a rt = lt . cons a . rt 

И левая складка:

foldTreeL :: (acc -> a -> acc) -> acc -> Tree a -> acc
foldTreeL conj z t = foldTree g id t z           -- b ~ acc -> acc
  where
  g lt a rt = rt . flip conj a . lt

Более подробные пояснения:

И cons a, и flip conj a имеют тип r -> r (или acc -> acc, что одно и то же). Это тип функций, в которых типы аргумента и результата совпадают.

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

(.) :: (b -> c) -> (a -> b) -> (a -> c)
(f . g) x = f (g x)

-- and for endofunctions,
-- ((f :: r -> r) . (g :: r -> r)) :: r -> r

Для дерева с последовательным обходом [a,b,c,...,n] правая складка превращает это дерево в композицию

(cons a . cons b . cons c . ..... . cons n) z

-- which is the same as
-- cons a (cons b (cons c ... (cons n z) ... ))

А левая складка превращает его в

(conj' n . ..... . conj' c . conj' b . conj' a) z

Где

conj' a acc = flip conj a acc = conj acc a     -- by definition of `flip`

-- so the above composition chain is the same as
-- conj (... (conj (conj (conj z a) b) c) ...) n

С некоторыми id, разбросанными по этой цепочке, каждый Leaf превращается в id, не влияя на всю цепочку, потому что

(id . f) x = id (f x) = f x = f (id x) = (f . id) x

Так

id . f = f = f . id

Т.е. id, служащий «нулевым» элементом относительно. к операции композиции функции, точно так же, как 0 делает с операцией + (это, кстати, упоминается как 'моноид' , формируемый {{X3} } и id, или 0 и +).


Вот как мы создадим этот список обхода по порядку для дерева:

inorder :: Tree a -> [a]
inorder t = foldTree g [] t
  where
  g lt a rt = lt ++ [a] ++ rt

Таким образом, список [a,b,...,n] фактически создается как

[] ++ [a] ++ [] ++ [b] ++ [] ++ ... ++ [n] ++ []

И правая складка этого дерева будет создана как

(id . cons a . id . cons b . id . .... . cons n . id) z
3
Will Ness 22 Сен 2018 в 02:22

Вот как вы могли придумать решение самостоятельно.

У нас есть

data Tree a =                               Leaf 
            | Node (Tree a) a (Tree a)                   deriving (Eq, Show)

foldTree :: (        b ->   a ->   b -> b) -> b -> Tree a -> b

foldTree    (g :: b ->   a ->   b -> b) (z :: b) :: Tree a -> b

Таким образом, с учетом g и z эта функция превращает значение Tree a в значение b, превращая поддеревья t в b s и объединяя их с деревом a через g.

Можем ли мы map поверх этих деревьев использовать foldTree? Да:

mapTree :: (a -> c) -> Tree a -> Tree c
mapTree f t = foldTree g z t
  where
  -- we need to create a Node with the mapped element inside it
  -- already having the transformed sub-trees. Well, 
  -- creating Tree values is the job of that type's data constructors:
  g lt a rt = Node lt (f a) rt      -- f is applied to the element `a`
  -- and all leaves are transformed into the same value, which is:
  z = Leaf

Итак, теперь у нас есть mapTree (f :: a -> c) :: Tree a -> Tree c. Как это нам помогает?

Что мы хотим? Мы хотим

foldTreeR :: (a -> b -> b) -> b -> Tree a -> b
-- i.e.
foldTreeR (cons :: a -> r -> r) (nil :: r) :: Tree a -> r

Итак, у нас есть cons, такое что cons (x :: a) :: r -> r.

Что, если мы сопоставим это cons с деревом? Типом a -> r -> r на самом деле является a -> (r -> r), и мы только что видели, что cons превращает (x :: a) в r -> r ( прочтите: превращает значение { {X6}} или введите a в значение типа r -> r ):

mapTree (cons :: a -> r -> r) :: Tree a -> Tree (r -> r)

Зачем нам это ? Что мы можем сделать со всеми этими r -> r функциями, которые теперь есть в узлах дерева? Что ж, мы можем превратить дерево в список значений в его узлах путем обхода по порядку:

inorder :: Tree d -> [d]
inorder t = foldTree (\l a r -> l ++ a : r) [] t

Так что мы можем иметь

inorder . mapTree (cons :: a -> r -> r) :: [r -> r]
          -----------------
          Tree a -> Tree (r -> r)
--------
Tree (r->r) -> [r->r]

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

foldTreeR_ :: (a -> r -> r) -> r -> Tree a -> r
foldTreeR_ cons z t = foldr ($) z (inorder $ mapTree cons t)
           -- or, equivalently,
           --         foldr (.) id (inorder $ mapTree cons t) z

Вот и все.

Если мы встроим все и упростим полученные определения, мы получим одно в другом ответе.

То же и для левого складывания. Попытайся.

2
Will Ness 22 Сен 2018 в 19:27