Функтор Haskell для красно-черного дерева

Итак, я изучаю Haskell, и у меня есть красно-черное дерево с разными типами в красных и черных узлах, реализованных следующим образом:

data Rbtree a1 b1 = EmptyTree | Node a1 (Rbtree b1 a1) (Rbtree b1 a1) deriving (Show, Read, Eq)

А теперь мне нужно определить для него экземпляр функтора. Поскольку Rbtree - это конструктор типа, который принимает два параметра, я должен создать экземпляр для Rbtree c. И после этого я застрял. Мой код сейчас выглядит примерно так:

instance Functor (Rbtree c) where
fmap f EmptyTree = EmptyTree
fmap f (Node x left right) = Node x (fmap f left) (fmap f right)

Как вы могли догадаться, это не компилируется. (ошибки компиляции). Я понимаю, что fmap, потому что это должно быть (a -> b) -> (Rbtree c) a -> (Rbtree c) b, и если посмотреть глубже на Node часть, это должно быть (a -> b) -> (Node c (Rbtree a c) (Rbree a c)) -> (Node c (Rbtree b c) (Rbree b c)). Я не понимаю, как развернуть left и right, чтобы я мог применить f только к его части. Я думаю, что мне что-то здесь не хватает.


person Genmilhas    schedule 15.05.2014    source источник
comment
взгляните на Bifunctor: hackage.haskell.org/package/bifunctors/ docs / Data-Bifunctor.html   -  person fizruk    schedule 15.05.2014
comment
Кроме того, ваше data определение красно-черного дерева не использует b1 - на самом деле у вас есть только a1 узлов.   -  person fizruk    schedule 15.05.2014
comment
@CrazyFIZRUK: обратите внимание, как у дочерних узлов перевернуты аргументы типа, что вызывает проблему.   -  person Xeo    schedule 15.05.2014
comment
@Xeo, верно, я пропустил эту часть.   -  person fizruk    schedule 15.05.2014
comment
Как насчет использования GADT для обеспечения всех инвариантов красно-черного дерева в типах? :) Не думаю, что это будет слишком хлопотно.   -  person Piotr Miś    schedule 15.05.2014
comment
@uraf Единственный инвариант, который вам нужно обеспечить (учитывая EmptyTree черный), - это то, что каждый путь содержит одинаковое количество черных узлов (поправьте меня, если я ошибаюсь). Но для этого вам понадобятся числа на уровне типа, верно? Тогда вам понадобится зависимая сумма (Σ-тип) для определения insert (поскольку иногда она увеличивает это связанное число, а иногда нет). Так что я полагаю, что GADT здесь не помогут.   -  person fizruk    schedule 15.05.2014
comment
@uraf о, вы действительно можете скрыть связанный номер за экзистенциальным, вот так: data MyRBTree a = forall n. MyRBTree (RBTree n a) и определить insert поверх MyRBTree, имитируя Σ-тип, как здесь: reddit.com/r/haskell/comments/ti5il/   -  person fizruk    schedule 15.05.2014
comment
@CrazyFIZRUK, да, я имел в виду именно это.   -  person Piotr Miś    schedule 15.05.2014


Ответы (3)


instance Functor (Rbtree c) where
  fmap = fmap_even where
     fmap_even _ EmptyTree = EmptyTree
     fmap_even f (Node x left right) = Node x (fmap_odd f left) (fmap_odd f right)
     fmap_odd  _ EmptyTree = EmptyTree
     fmap_odd  f (Node x left right) = Node (f x) (fmap_even f left) (fmap_even f right)

Ваше определение дерева RB не имеет для меня особого смысла, но на случай, если мне что-то не хватает, вот экземпляр Functor, совместимый с ним.

person n. 1.8e9-where's-my-share m.    schedule 15.05.2014
comment
'fmap_even' не является (видимым) методом класса 'Functor'. То же самое с fmap_odd. Действительно ли возможно определять методы в подобных случаях? Хотя мне это кажется правильной идеей. - person Genmilhas; 15.05.2014
comment
@Genmilhas, вы получаете эту ошибку для экземпляра Functor или пытаетесь вызвать fmap_even вне этого instance определения? - person fizruk; 15.05.2014
comment
@Crazy FIZRUK Я получаю это из экземпляра функтора. - person Genmilhas; 15.05.2014
comment
@Genmilhas Мне кажется, это ошибка отступа. У вашей копии одинаковый отступ fmap и fmap_even? Не должно быть. - person n. 1.8e9-where's-my-share m.; 15.05.2014

Вы можете сделать свой Rbtree a Bifunctor (см. bifunctors package) следующим образом:

import Data.Bifunctor

data Rbtree a1 b1 = EmptyTree | Node a1 (Rbtree b1 a1) (Rbtree b1 a1)

instance Bifunctor Rbtree where
  bimap _ _ EmptyTree = EmptyTree
  bimap f g (Node x l r) = Node (f x) (bimap g f l) (bimap g f r)

В этом экземпляре у вас теперь есть функции first и second для сопоставления красных или черных узлов (second ~ fmap). Фактически вы можете определить экземпляр Functor следующим образом:

instance Functor (Rbtree c) where
  fmap = second

Пример

>>> let t = Node 1 (Node "hello" EmptyTree EmptyTree) EmptyTree
>>> bimap show length t
Node "1" (Node 5 EmptyTree EmptyTree) EmptyTree
>>> fmap length t
Node 1 (Node 5 EmptyTree EmptyTree) EmptyTree
>>> first show t
Node "1" (Node "hello" EmptyTree EmptyTree) EmptyTree
person fizruk    schedule 15.05.2014

Вы можете принудительно применить все красно-черные инварианты дерева с помощью GADT и некоторые виды хакерства (количественная оценка существования, арифметика типов, типы данных). Свойства:

  1. Узел бывает либо красным, либо черным.
  2. Корень черный.
  3. Все листья (NIL) черные.
  4. Каждый красный узел должен иметь два черных дочерних узла.
  5. Каждый путь от данного узла к любому из его дочерних листьев содержит одинаковое количество черных узлов.

А вот пример кода:

{-# LANGUAGE GADTs, StandaloneDeriving, ExistentialQuantification,
             KindSignatures, DataKinds #-}

data Nat = Zero | Succ Nat
data Color = Red | Black

data Node :: Color -> Nat -> * -> * where
    Nil :: Node Black Zero a
    RedNode :: a -> Node Black n a -> Node Black n a -> Node Red n a
    BlackNode :: a -> Node c1 n a -> Node c2 n a -> Node Black (Succ n) a

data RBTree a = forall n. RBTree (Node Black n a)

deriving instance (Show a) => Show (Node c n a)
deriving instance (Show a) => Show (RBTree a)

instance Functor (Node c n) where
    fmap f Nil = Nil
    fmap f (RedNode   x l r) = RedNode   (f x) (fmap f l) (fmap f r)
    fmap f (BlackNode x l r) = BlackNode (f x) (fmap f l) (fmap f r)

instance Functor RBTree where
    fmap f (RBTree t) = RBTree (fmap f t)

Вы можете использовать это так:

tree = RBTree $ BlackNode 3 (RedNode 4 Nil Nil) (RedNode 5 Nil Nil)
main = print $ fmap (*5) tree

Результат:

RBTree (BlackNode 15 (RedNode 20 Nil Nil) (RedNode 25 Nil Nil))

Но это не скомпилируется:

tree = RBTree $ BlackNode 3 (RedNode 4 Nil Nil) (BlackNode 5 Nil Nil)

Вы получите красивое сообщение об ошибке:

Couldn't match type `Succ Zero' with `Zero'
Expected type: Node Black Zero a0
  Actual type: Node Black (Succ Zero) a0
In the return type of a call of `BlackNode'
In the third argument of `BlackNode', namely
  `(BlackNode 5 Nil Nil)'
In the second argument of `($)', namely
  `BlackNode 3 (RedNode 4 Nil Nil) (BlackNode 5 Nil Nil)'
person Piotr Miś    schedule 15.05.2014
comment
Вы можете сделать эту схему немного шикарнее, если добавите DataKinds; см. модуль в пакете тестов ghc github.com/ghc / ghc / blob / master / testsuite / tests / polykinds / - person Michael; 16.05.2014
comment
@Arthur, спасибо за предложение. Поскольку я довольно неопытный программист на Haskell, я не знал о существовании чего-то вроде DataKinds. :) - person Piotr Miś; 16.05.2014