[Haskell-cafe] Monad for binary tree data structure

wren ng thornton wren at freegeek.org
Sat Jul 23 23:45:37 CEST 2011


On 7/23/11 1:31 AM,
Александр wrote:
> Hello,
>
> I built binary tree with:
>
> data Tree a = Empty
>                | Node a (Tree a) (Tree a)
>                deriving (Eq, Ord, Read, Show)
>
> How can i make Monad type class instance for this tree? And can i make
it on not?

Of course you can. One way of thinking about monads is that it's just
performing substitution, aka tree grafting. In this case, because the
"variables" being substituted for are located within the tree, we need to
make sure to distribute the substructure of the original tree over the
result of substituting for the variable. Thus, we can define:

    data Tree a = Empty | Node a (Tree a) (Tree a)
        deriving (Eq, Ord, Read, Show)

    -- This one should be obvious.
    instance Functor Tree where
        fmap f Empty        = Empty
        fmap f (Node x l r) = Node (f x) (fmap f l) (fmap f r)

    -- | The tree @graft gl gr t@ is the result of grafting @gl@ in
    -- place of all left @Empty@ leaves and @gr@ in place of all
    -- right @Empty@ leaves; with the empty tree mapped to itself.
    graft :: Tree a -> Tree a -> Tree a -> Tree a
    graft gl gr = go
        where
        go Empty                = Empty
        go (Node x Empty Empty) = Node x gl gr
        go (Node x Empty r    ) = Node x gl (go r)
        go (Node x l     Empty) = Node x (go l) gr
        go (Node x l     r    ) = Node x (go l) (go r)

    instance Monad Tree where
        return x         = Node x Empty Empty
        Empty      >>= _ = Empty
        Node x l r >>= f = graft (l >>= f) (r >>= f) (f x)

And we can verify the correctness by using QuickCheck or lazy SmallCheck:

    import Control.Monad ((<=<))
    import Test.QuickCheck

    instance Arbitrary a => Arbitrary (Tree a) where
        arbitrary = do
            b <- elements [True,False]
            if b
                then return Empty
                else do
                    x <- arbitrary
                    l <- arbitrary
                    r <- arbitrary
                    return (Node x l r)

    -- | Extensional function equality.
    (f === g) x  =  f x == g x

    prop_mapIdentity     =           fmap id === id
    prop_mapCompose f g  = (fmap f . fmap g) === fmap (f . g)

    prop_leftUnit  f     =    (return <=< f) === f
    prop_rightUnit f     =    (f <=< return) === f
    prop_assoc     f g h = (f <=< (g <=< h)) === ((f <=< g) <=< h)


Of course, there are other monads as well. In particular, for any
semigroup on A there is a monad for Tree A. For these instances we would
want to define a merging function:

    -- | The first argument is an associative operation for resolving
    -- conflicts in merging. Non-conflicts are resolved by assuming
    -- an identity element for the operation, thus lifting it into a
    -- monoid.
    merge :: (a -> a -> a) -> Tree a -> Tree a -> Tree a
    merge _ Empty tr    = tr
    merge _ tl    Empty = tl
    merge f (Node x xl xr) (Node y yl yr) =
        Node (f x y) (merge f xl yl) (merge f xr yr)

Of course, there are only two semigroups which are parametric in A, hence
there are only two more Monad instances we can define this way.

    newtype Tree2 a = T2 (Tree a)
        deriving (Eq, Ord, Read, Show)

    instance Monad Tree2 where
        return x           = T2 $ Node x Empty Empty
        T2 Empty         >>= _ = T2 Empty
        T2(Node x xl xr) >>= f =
            case f x of
            T2 Empty          -> T2 Empty
            T2 (Node y yl yr) ->
                T2 $ Node y (merge' yl (T2 xl >>= f))
                            (merge' yr (T2 xr >>= f))
            where
            merge' xs (T2 ys) = merge const xs ys

    instance Arbitrary a => Arbitrary (Tree2 a) where
        arbitrary = arbitrary >>= (return . T2)

And we can similarly define Tree3 with (flip const) in place of const. All
the other monads formed this way (with any semigroup operation in place of
const) would require using a type class for restricted monads since we
need to restrict the element type to support the semigroup operation we
want to use.

-- 
Live well,
~wren




More information about the Haskell-Cafe mailing list