Search code examples
haskelltreemonadsstate-monad

Tree Insert using State Monad


I have a tree and insert operation defined as in "Learn You a Haskell for Great Good!" :

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

treeInsert :: (Ord a) => a -> Tree a -> Tree a  
treeInsert x EmptyTree = Node x EmptyTree EmptyTree
treeInsert x (Node a left right)   
    | x == a = Node x left right  
    | x < a  = Node a (treeInsert x left) right  
    | x > a  = Node a left (treeInsert x right)   

I would like to reimplement treeInsert using State Monad, but I'm not even sure how function declaration should look like. I have this so far:

treeInsert :: (Ord a) => a -> Tree a -> State (Tree a) a

How would you write treeInsert using State Monad?


Solution

  • Warning: This answer contains spoilers.

    You can fairly easily write a wrapper around your existing treeInsert function that allows you do use do-notation the way you want. As per the comments, there's a function modify that takes a modifying function f :: s -> s and turns it into a State s () which is an "action" to modify a state s. That means you can write:

    stateTreeInsert :: (Ord a) => a -> State (Tree a) ()
    stateTreeInsert x = modify (treeInsert x)
    

    or more succinctly:

    stateTreeInsert :: (Ord a) => a -> State (Tree a) ()
    stateTreeInsert = modify . treeInsert
    

    Then, you can define an "action" like:

    insertSomeStuff :: (Ord a, Num a) => State (Tree a) ()
    insertSomeStuff = do
      stateTreeInsert 0
      stateTreeInsert 1
      stateTreeInsert 2
    

    and then apply it to a particular tree using execState:

    main = print $ execState insertSomeStuff EmptyTree
    

    However, I guess you were more interested in re-implementing treeInsert from scratch in a state-manipulating form.

    The problem is that the "straightforward" way of doing this isn't very interesting or idiomatic. It's just awkward. It would look something like this:

    awkwardTreeInsert :: (Ord a) => a -> State (Tree a) ()
    awkwardTreeInsert x = do
      t <- get
      case t of
        EmptyTree -> put $ Node x EmptyTree EmptyTree
        Node a l r -> case compare x a of
          LT -> do put l                 -- replace tree with left child
                   awkwardTreeInsert x   -- insert into left child
                   l' <- get             -- get the answer
                   put $ Node a l' r     -- overwrite with whole tree w/ updated left child
          GT -> do put r
                   awkwardTreeInsert x
                   r' <- get
                   put $ Node a l r'
          EQ -> return ()
    

    The issue here is that the state, as we've written it, can only hold one tree at once. So, if we want to call the algorithm recursively to insert something into a branch, we need to overwrite the "big tree" with one of its children, run the recursive insertion, get the answer, and overwrite it with the "big tree" with the appropriate child replaced.

    Anyway, it works the same way as stateTreeInsert so:

    insertSomeStuff :: (Ord a, Num a) => State (Tree a) ()
    insertSomeStuff = do
      awkwardTreeInsert 0
      awkwardTreeInsert 1
      awkwardTreeInsert 2
    
    main = print $ execState insertSomeStuff EmptyTree