
Hi All, In the best spirit of Haskelling, I thought I'd try dropping in a completely different data structure in a spot where I thought the existing one was (1) ugly (2) leaking memory. In particular, I wrote a Trie implementation. Now the point is actually not much to do with the data structure itself, but code layout. I mention this particular data structure only because it is the one I was working on, but it seems to come up quite often. Consider the following function: data Trie t = Empty | Trie (TriePtr t) (MaybePtr t) (TriePtr t) type TriePtr t = TVar (Trie t) type MaybePtr t = TVar (Maybe t) data Bit = Zero | One deriving Show dmin p = do mv <- dmin' p case mv of Nothing -> error "dmin: no values" Just (v,_) -> return v dmin' p = do t <- readTVar p case t of Empty -> return Nothing Trie l m r -> do mv <- dmin' l case mv of Nothing -> do mv <- readTVar m case mv of Nothing -> do mv <- dmin' r case mv of Nothing -> error "emit nasal daemons" Just (v,e) -> do if e then writeTVar p Empty else return () return mv Just v -> do re <- null r case re of False -> writeTVar m Nothing True -> writeTVar p Empty return (Just (v,re)) Just (v,e) -> do case e of True -> do me <- empty m re <- null r case me && re of False -> writeTVar m Nothing True -> writeTVar p Empty return (Just (v,me && re)) False -> return mv where empty m = do v <- readTVar m case v of Nothing -> return True Just _ -> return False All that case analysis causes indentation to creep, and lots of vertical space "feels" wasted. Is that just a fact of life, or is there Haskellmagic that I still need to learn? cheers, T. -- Dr Thomas Conway drtomc@gmail.com Silence is the perfectest herald of joy: I were but little happy, if I could say how much.

Hi
In particular, I wrote a Trie implementation.
Neat, I often feel I should be using one of those, but never do because Data.Map is just too temptingly close by. A couple of the combinators can be used to simplify some bits:
case v of Nothing -> return True Just _ -> return False
becomes: < isNothing v
if e then writeTVar p Empty else return ()
< when e $ writeTVar p Empty There all so seem to be some other common bits:
case re of False -> writeTVar m Nothing True -> writeTVar p Empty
Perhaps that could become a function? Thanks Neil

On Jul 13, 2007, at 16:59 , Thomas Conway wrote:
case re of False -> writeTVar m Nothing True -> writeTVar p Empty
uncurry writeTVar $ if re then (p,Empty) else (m,Nothing)
case v of Nothing -> return True Just _ -> return False
return (isJust v)
-- brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH

On Jul 13, 2007, at 18:56 , Brandon S. Allbery KF8NH wrote:
return (isJust v)
*blush* Invert that, of course: isNothing v -- brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH

On 2007-07-13, Thomas Conway
Hi All,
In the best spirit of Haskelling, I thought I'd try dropping in a completely different data structure in a spot where I thought the existing one was (1) ugly (2) leaking memory. In particular, I wrote a Trie implementation. Now the point is actually not much to do with the data structure itself, but code layout. I mention this particular data structure only because it is the one I was working on, but it seems to come up quite often.
Consider the following function:
data Trie t = Empty | Trie (TriePtr t) (MaybePtr t) (TriePtr t) type TriePtr t = TVar (Trie t) type MaybePtr t = TVar (Maybe t)
It might be a bit clearer if every level of the tree were a flat map of pointers. You can even parametrize on this map type... -- Aaron Denney -><-

On 7/14/07, Aaron Denney
It might be a bit clearer if every level of the tree were a flat map of pointers. You can even parametrize on this map type...
Yes, this would be an obvious generalization, though if I were to modify the details of the structure, I'd be inclined to go in exactly the opposite direction, and rather than have the keys be [Bit], use Bits b => .... and use an Int argument to recurse down the tree. The motivation for this structure is that I wanted a queue, from which I could remove elements from the middle efficiently, and using only local updates (for high concurrency). The structure I was replacing used a doubly linked list using TVars as pointers between nodes. As I hinted in the original post, this was ugly, and seem to be leaking memory (I actually think there might be some issues with the GHC implementation of TVars and GC - I'm not certain, but I think the leak *may* be a bug in GHC, and as I posted separately, GC was taking an awfully large proportion of the time). One way of achieving what I wanted was to keep a "timestamp" counter and use (Map TimeStamp k). The problem with Map is that it is hostile to concurrency - all modifications are "global" wrt the Map. The structure that is required in this instance is a structure with enough TVars linking the pieces that updates are local - a write to one TVar doesn't interact with reads in other parts of the structure. For example a binary tree with TVars between the nodes. Except a (vanilla) binary tree would be rotten in this case because the new keys arrive in strictly increasing order - a worst case for such a structure. So I could have modified Map, or my own home-rolled AVL tree to stick TVars between the nodes, there were reasons not to: 1. Tries are easy to implement and offer O(b) ~= O(1) for keys with b bits. Thanks to apfelmus for reminding me of this recently. 2. In Haskell, it's *fun* rolling new data structures to see how elegant you can be. (A favorite quote of mine is "Elegance is not Optional", I think due to Richard O'Keefe.) 3. This structure is used in an inner loop, so a structure giving O(1) operations was desirable. Anyway, the point of the original post was to find tricks for avoiding indentation creep, rather than the trie itself. cheers, T. -- Dr Thomas Conway drtomc@gmail.com Silence is the perfectest herald of joy: I were but little happy, if I could say how much.

is there Haskellmagic that I still need to learn?
one bit of magic, coming right up!-) of course, we haskellers are the lazy types, and if none of that helps, some Monad often does. in ghci or hugs, try ':browse Data.Maybe' and ':info Maybe'. in particular, 'case . of Nothing -> . ; Just . -> .' calls for 'maybe', and nested chains of such cases call for 'Monad Maybe' and, possibly, 'MonadPlus Maybe' (the latter from 'Control.Monad'). consider these two (try unfolding the definitions, and compare with your code): maybe (error "nothing") id (lookup False []) do { it <- lookup False []; return it }`mplus` return 42 hmm. now, how do i point to the source for that? anyway, here it is: instance Monad Maybe where (Just x) >>= k = k x Nothing >>= k = Nothing return = Just fail s = Nothing instance MonadPlus Maybe where mzero = Nothing Nothing `mplus` ys = ys xs `mplus` ys = xs hth, claus

In addition to what's already been pointed out, note that this:
do t <- readTVar p case t of Empty -> return Nothing Trie l m r -> do <stuff>
Is a case of the (non-existent) MaybeT transformer:
do Trie l m r <- readTVar p <stuff with slight modifications>
The modifications being something like 'return . Just' => 'return', and 'return Nothing' => mzero.
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
instance Functor f => Functor (MaybeT f) where fmap f = MaybeT . fmap (fmap f) . runMaybeT
instance Monad m => Monad (MaybeT m) where return = MaybeT . return . Just m >>= f = MaybeT $ runMaybeT m >>= (runMaybeT . f) fail _ = MaybeT $ return Nothing
instance Monad m => MonadPlus (MaybeT m) where mzero = MaybeT $ return Nothing m1 `mplus` m2 = MaybeT $ liftM2 mplus (runMaybeT m1) (runMaybeT m2)
(I haven't tested the code, but that's approximately what it looks like; let me know if I did something wrong and you need it fixed; I suspect you won't, as I'm not sure it simplifies the remained of <stuff> any :)) -- Dan

Thomas Conway wrote:
The motivation for this structure is that I wanted a queue, from which I could remove elements from the middle efficiently,
Anyway, the point of the original post was to find tricks for avoiding indentation creep, rather than the trie itself.
Knowing that it's a trie to be used as priority queue makes things a lot easier, no need to figure out myself what exactly dmin does :) Dan Doel wrote:
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
instance Monad m => MonadPlus (MaybeT m) where mzero = MaybeT $ return Nothing m1 `mplus` m2 = MaybeT $ liftM2 mplus (runMaybeT m1) (runMaybeT m2)
lift :: Monad m => m a -> MaybeT m a lift = MaybeT . liftM Just The Maybe monad transformer does the job, `mplus` is what you want: deletemin :: TVar (Trie t) -> STM (Maybe t) deletemin = runMaybeT delmin' where delminMaybe p = readTVar p >>= \t -> case t of Nothing -> mzero Just v -> (lift $ writeTVar p Nothing) >> return v delmin' p = readTVar p >>= \t -> case t of Empty -> mzero Trie l m r -> delmin' l `mplus` delminMaybe m `mplus` delmin' r `mplus` (lift (writeTVar p Empty) >> mzero) Note that the step of replacing a trie with empty children with the constructor Empty is delayed since this is nicer to write down :) Regards, apfelmus

Hello Thomas, Saturday, July 14, 2007, 12:59:16 AM, you wrote:
case re of False -> writeTVar m Nothing True -> writeTVar p Empty
All that case analysis causes indentation to creep, and lots of vertical space "feels" wasted. Is that just a fact of life, or is there Haskellmagic that I still need to learn?
this code actually looks as core Haskell, to which all Haskell code desugared by compiler :) i will not add too much to answers al;ready given, but when your function look too long - split it into several ones. it's one more way to decrease indenting. you may use local functions via 'where' or 'let' if you don't want to make them global -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

G'day.
One small suggestion.
Quoting Thomas Conway
Just (v,e) -> do case e of True -> [...] False -> [...]
This works just as well: Just (v,True) -> [...] Just (v,False) -> [...] And given that you wrote the corresponding code in another compiler that we won't mention, I feel justified in saying: Shame on you for not spotting that one. :-) Cheers, Andrew Bromage

TC> dmin p = do TC> mv <- dmin' p TC> case mv of TC> Nothing -> error "dmin: no values" TC> Just (v,_) -> return v TC> dmin' p = do TC> t <- readTVar p TC> case t of TC> Empty -> return Nothing TC> Trie l m r -> do TC> mv <- dmin' l TC> case mv of TC> Nothing -> do TC> mv <- readTVar m TC> case mv of TC> Nothing -> do TC> mv <- dmin' r TC> case mv of TC> Nothing -> error "emit nasal daemons" TC> Just (v,e) -> do TC> if e TC> then writeTVar p Empty TC> else return () TC> return mv TC> Just v -> do TC> re <- null r TC> case re of TC> False -> writeTVar m Nothing TC> True -> writeTVar p Empty TC> return (Just (v,re)) TC> Just (v,e) -> do TC> case e of TC> True -> do TC> me <- empty m TC> re <- null r TC> case me && re of TC> False -> writeTVar m Nothing TC> True -> writeTVar p Empty TC> return (Just (v,me && re)) TC> False -> return mv TC> where TC> empty m = do TC> v <- readTVar m TC> case v of TC> Nothing -> return True TC> Just _ -> return False data MaybeT m a = MaybeT {runMaybeT :: m (Maybe a)} fromMaybeT dflt handle (MaybeT mmx) = mmx >>= maybe dflt handle instance Monad m => Monad (MaybeT m) where return x = MaybeT $ return $ Just x MaybeT mmx >>= f = MaybeT $ mmx >>= maybe (return Nothing) (runMaybeT . f) instance Monad m => MonadPlus (MaybeT m) where mzero = MaybeT $ return mzero MaybeT mmx `mplus` MaybeT mmy = MaybeT $ liftM2 mplus mmx mmy instance MonadTrans MaybeT where lift mx = MaybeT $ liftM return mx dmin p = fromMaybeT (error "dmin: no values") (\(v,_) -> return v) $ dmin' p dmin' p = do null' p >>= guard Trie l m r <- readMbTVar p (do mv@(v,e) <- dmin' l (do guard e me <- empty m re <- null' r let b = me && re if b then writeMbTVar p Empty else writeMbTVar m Nothing return (v,b)) `mplus` return mv) `mplus` (do v <- MaybeT $ readTVar m re <- null' r if re then writeMbTVar m Nothing else writeMbTVar p Empty return (v,re)) `mplus` (do mv@(v,e) <- dmin' r when e $ writeMbTVar p Empty return mv) `mplus` error "emit nasal daemons" where readMbTVar x = lift $ readTVar x writeMbTVar x y = lift $ writeTVar x y empty m = liftM isNothing $ readMbTVar m null' p = lift $ null p

On 7/15/07, Miguel Mitrofanov

Everyone's suggestions show that in order to advance to a level 3 Haskell Mage[*], I need to spend a chunk of time learning to grok monad transformers.
let's see whether we can get from the initial version to the suggested final version without any magic, in a somewhat long sequence of minor rewrites/refactorings. i won't list all intermediate stages (the derivation is long enough as it is), and i hope that readers will find this interesting in spite of its length (you might want to load the initial version into your editor and follow along as you read the refactoring notes below). enjoy (i hope:-), claus --------------------------------------------- initial version dmin p = do mv <- dmin' p case mv of Nothing -> error "dmin: no values" Just (v,_) -> return v dmin' p = do t <- readTVar p case t of Empty -> return Nothing Trie l m r -> do mv <- dmin' l case mv of Nothing -> do mv <- readTVar m case mv of Nothing -> do mv <- dmin' r case mv of Nothing -> error "emit nasal daemons" Just (v,e) -> do if e then writeTVar p Empty else return () return mv Just v -> do re <- nullT r case re of False -> writeTVar m Nothing True -> writeTVar p Empty return (Just (v,re)) Just (v,e) -> do case e of True -> do me <- empty m re <- nullT r case me && re of False -> writeTVar m Nothing True -> writeTVar p Empty return (Just (v,me && re)) False -> return mv where nullT :: Monad m => TriePtr t -> m Bool nullT t = undefined empty m = do v <- readTVar m case v of Nothing -> return True Just _ -> return False --------------------------------------------- initial version simple things first: in dmin: replace case with maybe use =<< to avoid intermediate mv replace lambda with (return . fst) in empty: replace case with maybe lift return out of the branches use =<< to avoid intermediate v 'maybe True (const False)' is (Data.Maybe) isNothing use liftM to apply isNothing in dmin': use (Control.Monad) 'when e .' to replace 'if e then . else return ()' create and use (2x) function 'write' write m p (v,False) = writeTVar m Nothing >> return (Just (v,False)) write m p (v,True ) = writeTVar p Empty >> return (Just (v,True)) now, on to slightly bigger rewrites: inside-out, replace 'case . of Nothing -> .; Just . -> .' with maybe case mv of Nothing -> error "emit nasal daemons" Just (v,e) -> do when e $ writeTVar p Empty return mv becomes maybe (error "emit nasal daemons") (\(v,e) -> do when e $ writeTVar p Empty return mv) mv and so on, for all three levels of case (in the outermost case, one 'return mv' needs to be replaced with 'return (Just (v,e))', we'll do the same for the other 'return mv', for clarity) at this stage, the code looks somewhat like this: dmin p = maybe (error "dmin: no values") (return . fst) =<< dmin' p dmin' p = do t <- readTVar p case t of Empty -> return Nothing Trie l m r -> do mv <- dmin' l maybe (do mv <- readTVar m maybe (do mv <- dmin' r maybe (error "emit nasal daemons") (\(v,e) -> do when e $ writeTVar p Empty return (Just (v,e))) mv) (\v -> do re <- nullT r write m p (v,re)) mv) (\(v,e) -> do case e of True -> do me <- empty m re <- nullT r write m p (v,me && re) False -> return (Just (v,e))) mv where write m p (v,False) = writeTVar m Nothing >> return (Just (v,False)) write m p (v,True ) = writeTVar p Empty >> return (Just (v,True)) nullT :: Monad m => TriePtr t -> m Bool nullT t = undefined empty m = liftM isNothing $ readTVar m we'd still like to get rid of the nesting, and we see the pattern action >>= maybe (nontrivialB) (nontrivialA) repeatedly, which strongly suggests the use of (MonadPlus) 'mplus' (action >>= nontrivialA) `mplus` nontrivialB the problem is that those Maybes are interleaved with STM operations. as a first step, we can define our own 'mplus' for the special case of 'STM (Maybe a)', where we want the alternatives to be controlled by the Maybe result of the outer monad (STM in this case): a `mplus` b = (a >>= maybe b (return . Just)) however, our pattern is slightly more complex: there's always another STM operation to be executed first (readTVar or dmin'), and the result of that operation selects the branch, so we also need to define our own version of sequential composition: a >>> b = a >>= maybe (return Nothing) b now, we can rewrite the pattern do { v<-op; maybe that this v } to, using our own combinator versions, (op >>> this) `mplus` that so that do mv <- dmin' r maybe (error "emit nasal daemons") (\(v,e) -> do when e $ writeTVar p Empty return (Just (v,e))) mv turns into (dmin' r >>> (\ (v,e) -> do when e $ writeTVar p Empty return (Just (v,e)))) `mplus` (error "emit nasal daemons") again, we apply this rewriting inside out to all three levels of maybe, which gives us something like this code: dmin' p = do t <- readTVar p case t of Empty -> return Nothing Trie l m r -> (dmin' l >>> (\(v,e) -> do case e of True -> do me <- empty m re <- nullT r write m p (v,me && re) False -> return (Just (v,e)))) `mplus` ((readTVar m >>> (\v -> do re <- nullT r write m p (v,re))) `mplus` ((dmin' r >>> (\ (v,e) -> do when e $ writeTVar p Empty return (Just (v,e)))) `mplus` (error "emit nasal daemons"))) where a `mplus` b = (a >>= maybe b (return . Just)) a >>> b = a >>= maybe (return Nothing) b write m p (v,False) = writeTVar m Nothing >> return (Just (v,False)) write m p (v,True ) = writeTVar p Empty >> return (Just (v,True)) nullT :: Monad m => TriePtr t -> m Bool nullT t = undefined empty m = liftM isNothing $ readTVar m which already gets rid of most of the indentation creep. next, we want to turn our local combinators into proper Monad/MonadPlus instances, to avoid confusion and to get back the do-notation. since both these classes are defined over type constructors, rather than plain types, we need a type constructor that captures the composition of STM and Maybe in 'STM (Maybe a)'. actually, our combinators only depend on the composition of some Monad m with Maybe: data MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) } the Monad instance is almost exactly what we expect, using the definition of >>> we already have, with some added wrapping and unwrapping for our "type constructor composition constructor" (aka monad transformer;-): instance Monad m => Monad (MaybeT m) where return = MaybeT . return . Just a >>= b = MaybeT $ runMaybeT a >>= maybe (return Nothing) (runMaybeT . b) the MonadPlus instance is just what we expect, using our mplus definition with some extra wrapping and unwrapping. instance Monad m => MonadPlus (MaybeT m) where mzero = MaybeT $ return Nothing a `mplus` b = MaybeT $ runMaybeT a >>= maybe (runMaybeT b) (return . Just) now, before we can apply our shiny new instances to our code, there is the issue of plain STM operations like writeTVar and readTVar. when running code in our composed monad, we still want to be able to run operations in the wrapped inner monad. the standard way to do that is to define a 'lift' operation for lifting inner monad operations to the composed monad. so standard, in fact, that there is a class for this, (Control.Monad.Trans) MonadTrans, and we only need to define an instance for our wrapper: instance MonadTrans MaybeT where lift m = MaybeT $ m >>= return . Just to prepare for our next step, we apply lift to all barebones STM operations, readTVar, write, empty, nullT. at this stage, our types (asking ghci, with :t dmin') are slightly redundant: dmin' :: (MonadTrans t1, Monad (t1 STM)) => TVar (Trie t) -> t1 STM (Maybe (t, Bool)) since our particular MonadTrans, MaybeT, already wraps results in Maybe, this is one level of Maybe too much. so, when we remove our local definitions of mplus and >>> (replacing >>> with >>=), we remove that extra layer of Maybe, by removing the redundant (Just _) in returns, and by replacing 'return Nothing' with 'mzero'. we could now declare the type as dmin' :: TVar (Trie t) -> MaybeT STM (Maybe t, Bool) to retain compatibility, we also need to apply runMaybeT in dmin, unwrapping (dmin' p). after all that refactoring, the code should look something like this: dmin p = maybe (error "dmin: no values") (return . fst) =<< runMaybeT (dmin' p) dmin' p = do t <- lift $ readTVar p case t of Empty -> mzero Trie l m r -> (dmin' l >>= (\ (v,e) -> do case e of True -> do me <- lift $ empty m re <- lift $ nullT r lift $ write m p (v,me && re) False -> return (v,e))) `mplus` (((lift $ readTVar m) >>= (\ v -> do re <- lift $ nullT r lift $ write m p (v,re))) `mplus` ((dmin' r >>= (\ (v,e) -> do when e $ lift $ writeTVar p Empty return (v,e))) `mplus` (error "emit nasal daemons"))) where write m p (v,False) = writeTVar m Nothing >> return (v,False) write m p (v,True ) = writeTVar p Empty >> return (v,True) nullT :: Monad m => TriePtr t -> m Bool nullT t = undefined empty m = liftM isNothing $ readTVar m to clean up, we reapply do-notation instead of >>=, drop some redundant parentheses for mplus, and move the lift calls to the definitions of empty, nullT, etc., creating lifted variants readTVar' and writeTVar'. next, we can make use of the fact that pattern match failure in do-notation invokes fail in the monad, by defining 'fail msg = mzero' in our wrapped monad, and by pattern matching directly on the result of the first readTVar' (we only need the Trie-case, the other case will fail to match, leading to mzero, which is what we wanted anyway). we can also replace the remaining 'case e of True ..' by appealing to 'guard e' and mzero. at which stage our code looks sufficiently similar to Miguel's. we still don't need to have any idea what the code is supposed to do, as long as we haven't made any mistakes in refactoring, the final version should do the same thing as the initial version. usually, one would use a testsuite or a proven tool to monitor the steps, whereas my only test was "does it still compile?", which gives no assurance that the code transformations were indeed refactorings. no magic involved, just repeated simplifications, generalizations, and use of sufficiently advanced technology!-) by noticing that there was something about your code you didn't like, and looking for improvements, you've already done the most important step. as long as you remain determined to keep reviewing and simplifying your code, the route to "higher levels" isn't all that steep. part of the reason why i take part in such rewrite exercises on this list is to hone my own skills - there is always something more to learn;-) --------------------------------------------- final version dmin p = maybe (error "dmin: no values") (return . fst) =<< runMaybeT (dmin' p) dmin' p = do Trie l m r <- readTVar' p (do (v,e) <- dmin' l (do guard e me <- empty m re <- nullT r write m p (v,me && re)) `mplus` return ((v,e))) `mplus` (do v <- readTVar' m re <- nullT r write m p (v,re)) `mplus` (do (v,e) <- dmin' r when e $ writeTVar' p Empty return ((v,e))) `mplus` error "emit nasal daemons" where readTVar' var = lift $ readTVar var writeTVar' var val = lift $ writeTVar var val write m p (v,False) = lift $ writeTVar m Nothing >> return ((v,False)) write m p (v,True ) = lift $ writeTVar p Empty >> return ((v,True)) nullT :: Monad m => TriePtr t -> m Bool nullT t = undefined empty m = lift $ liftM isNothing $ readTVar m data MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) } instance Monad m => Monad (MaybeT m) where return = MaybeT . return . Just a >>= b = MaybeT $ runMaybeT a >>= maybe (return Nothing) (runMaybeT . b) fail msg= mzero instance Monad m => MonadPlus (MaybeT m) where mzero = MaybeT $ return Nothing a `mplus` b = MaybeT $ runMaybeT a >>= maybe (runMaybeT b) (return . Just) instance MonadTrans MaybeT where lift m = MaybeT $ m >>= return . Just --------------------------------------------- final version

On 7/16/07, Claus Reinke
enjoy (i hope:-),
Very much. Work hasn't been so much fun since.... Well, for a long time. :-) One small question, which might reveal if I've understood things aright.
(do (v,e) <- dmin' l (do guard e me <- empty m re <- nullT r write m p (v,me && re)) `mplus` return ((v,e))) `mplus` (do v <- readTVar' m re <- nullT r write m p (v,re)) `mplus` (do (v,e) <- dmin' r when e $ writeTVar' p Empty return ((v,e))) `mplus` error "emit nasal daemons"
If I refactor this a little into dminLeft dminMiddle and dminRight, I believe I should be able to replace this with: ... msum [dminLeft l m r, dminMiddle m r, dminRight r, error "emit nasal daemons"] where dminLeft l m r = do (v,e) <- dmin' l (do guard e me <- empty m re <- nullT r write m p (v,me && re)) `mplus` return (v,e) dminMiddle m r = do v <- readTVar' m re <- nullT r write m p (v,re) dminRight r = do (v,e) <- dmin' r when e $ writeTVar' p Empty return (v,e) .... Is this correct? And if so, is GHC likely to do constant folding on msum over the list skeleton? T. -- Dr Thomas Conway drtomc@gmail.com Silence is the perfectest herald of joy: I were but little happy, if I could say how much.

CR> let's see whether we can get from the initial version to the CR> suggested final version without any magic, in a somewhat long CR> sequence of minor rewrites/refactorings. Great! Thanks a lot, Claus. It's always interesting and enjoyable to see somebody explaining your own way of reasoning (well, not exactly, but the idea is the same). CR> next, we can make use of the fact that pattern match failure in CR> do-notation invokes fail in the monad, by defining 'fail msg = CR> mzero' My bad, I forgot about "fail". CR> whereas my only test was "does it still compile?", So was mine. CR> which gives no assurance that the code transformations were indeed CR> refactorings.

as Thomas pointed out off-list, the transformation sequence as given is not type-preserving. i even documented that problem in my email, because i thought the type was dodgy, but forgot to track it down before posting. so here are the changes. a good demonstration that "does it still compile?" is not a sufficient test for refactoring!-) claus
to prepare for our next step, we apply lift to all barebones STM operations, readTVar, write, empty, nullT. at this stage, our types (asking ghci, with :t dmin') are slightly redundant:
dmin' :: (MonadTrans t1, Monad (t1 STM)) => TVar (Trie t) -> t1 STM (Maybe (t, Bool))
since our particular MonadTrans, MaybeT, already wraps results in Maybe, this is one level of Maybe too much. so, when we remove our local definitions of mplus and >>> (replacing >>> with >>=), we remove that extra layer of Maybe, by removing the redundant (Just _) in returns, and by replacing 'return Nothing' with 'mzero'.
we also need to take into account that the second readTVar already returns a Maybe, so we only need to wrap it in MaybeT, without applying the full lift.
we could now declare the type as
dmin' :: TVar (Trie t) -> MaybeT STM (Maybe t, Bool)
there's that dodgy type. it should just be: dmin' :: TVar (Trie t) -> MaybeT STM (t, Bool)
after all that refactoring, the code should look something like this:
dmin p = maybe (error "dmin: no values") (return . fst) =<< runMaybeT (dmin' p)
dmin' p = do t <- lift $ readTVar p case t of Empty -> mzero Trie l m r -> (dmin' l >>= (\ (v,e) -> do case e of True -> do me <- lift $ empty m re <- lift $ nullT r lift $ write m p (v,me && re) False -> return (v,e))) `mplus` (((lift $ readTVar m) >>=
it was the return-wrapping of lift that introduced the extra Maybe here. this TVar already holds Maybes, so this should just be: `mplus` (((MaybeT $ readTVar m) >>=
next, we can make use of the fact that pattern match failure in do-notation invokes fail in the monad, by defining 'fail msg = mzero' in our wrapped monad, and by pattern matching directly on the result of the first readTVar' (we only need the Trie-case, the other case will fail to match, leading to mzero, which is what we wanted anyway).
we can also use this feature to replace the "half-lifted" second readTVar with a fully lifted readTVar' followed by a pattern match on 'Just v'.
--------------------------------------------- final version dmin p = maybe (error "dmin: no values") (return . fst) =<< runMaybeT (dmin' p)
dmin' p = do Trie l m r <- readTVar' p (do (v,e) <- dmin' l (do guard e me <- empty m re <- nullT r write m p (v,me && re)) `mplus` return ((v,e))) `mplus` (do v <- readTVar' m
by employing pattern-match failure handling, this can become: `mplus` (do Just v <- readTVar' m
re <- nullT r write m p (v,re)) `mplus` (do (v,e) <- dmin' r when e $ writeTVar' p Empty return ((v,e))) `mplus` error "emit nasal daemons" where readTVar' var = lift $ readTVar var writeTVar' var val = lift $ writeTVar var val
write m p (v,False) = lift $ writeTVar m Nothing >> return ((v,False)) write m p (v,True ) = lift $ writeTVar p Empty >> return ((v,True))
nullT :: Monad m => TriePtr t -> m Bool nullT t = undefined
empty m = lift $ liftM isNothing $ readTVar m
data MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
instance Monad m => Monad (MaybeT m) where return = MaybeT . return . Just a >>= b = MaybeT $ runMaybeT a >>= maybe (return Nothing) (runMaybeT . b) fail msg= mzero
instance Monad m => MonadPlus (MaybeT m) where mzero = MaybeT $ return Nothing a `mplus` b = MaybeT $ runMaybeT a >>= maybe (runMaybeT b) (return . Just)
instance MonadTrans MaybeT where lift m = MaybeT $ m >>= return . Just
--------------------------------------------- final version
participants (10)
-
Aaron Denney
-
ajb@spamcop.net
-
apfelmus
-
Brandon S. Allbery KF8NH
-
Bulat Ziganshin
-
Claus Reinke
-
Dan Doel
-
Miguel Mitrofanov
-
Neil Mitchell
-
Thomas Conway