I would like Haskell better if it didn’t do its best to make me feel stupid. Tasks that are easy in other languages, such, say, maintaining some state or generating a random number, become difficult. After banging my head on the State monad I finally arrived at a kind of understanding, which I put here so I can restore context in the future!
Presume that you’ve got a function that, given a list, returns a list of booleans corresponding to whether the value was the first occurrence in the list. In Python it would look something like this:
def is_first(lst): found = set() r = [] for item in lst: r.append(item not in found) found.add(item) return rFor example:
>>> is_first([1,2,3,1,4]) [True, True, True, False, True]The most simplistic way of converting this to Haskell would require using recursion for the looping, resulting in the following (non-tail-recursive) function:
first lst = first' lst [] first' [] _ = [] first' (h:t) state = if h `elem` found then False:(first' t state) else True:(first' t (h:state))The first’ function needs to “carry around” state as it loops around. Now, say that the lookup function is more complicated than we thought, so we break it out into a separate function. We have to pass around the state to all functions that access it, resulting in:
first lst = first' lst [] first' [] _ = [] first' (h:t) state = if (isfound h state) then False:(first' t state) else True:(first' t (h:state)) isfound h state = h `elem` stateSince isfound references the state, then it needs to be passed in explicitly.
I think that my main problem with understanding the State monad was that I kept wanting to treat the state as a global variable; so, I was thinking of “s <- get” and “put s” as references to a global stored somewhere, which left me with the question of “where is the initial put that sets the state?” Instead, though, you should think of “s <- get” and “put s” as references to an implicitly passed parameter.
The State Monad allows this particular pattern to be changed so that the state doesn’t need to be explicitly passed. Rewriting:
state_first lst = evalState (state_first' lst) [] state_first' [] = return [] state_first' (h:t) = do state <- get let found = h `elem` state put (if found then state else (h:state)) rest <- state_first' t return $ (not found):restBasically, given a Haskell function, you can convert it into the state monad by:
Now, this doesn’t seem more elegant, but note that if you have to break functionality out, the state parameter no longer needs to be explicitly passed:
state_first lst = evalState (state_first' lst) [] state_first' [] = return [] state_first' (h:t) = do state <- get found <- isfound h put (if found then state else (h:state)) rest <- state_first' t return $ (not found):rest isfound h = do state <- get return $ h `elem` stateUPDATED: following a question on #haskell (which was very friendly, especially to a complete newcomer), the following more elegant ways of solving this came up:
-- using mapAccum first lst = snd $ mapAccumL first' [] lst where first' acc x = (if found then acc else x:acc, not found) where found = x `elem` acc -- with mapM (not exactly equivalent to example) first lst = evalState first' [] where first' = mapM (\a -> do state <- get; put (a:state); return $ not (a `elem` state)) lst -- with mapM, equivalent to example first lst = evalState first' [] where first' = mapM isfound lst isfound a = do state <- get let found = a `elem` state put (if found then state else (a:state)) return $ not foundFor more information:
Roll Your Own IRC
Bot
Grok Haskell Monad
Transformers
Monads For The Working Haskell
Programmer
Haskell Monads: Another
View
Comments are moderated whenever I remember that I have a blog.