
Jules Bean:
If you're merely talking about top-down or bottom-up then there is also 'where' rather than 'let'.
Yes, I admit I tend to prefer "where" over "let", all else being equal. But my main concern was embedding recursive functions in do-blocks, particularly monadic loops that aren't amenable to the usual combinators. Bindings made in a do block aren't visible in a "where" clause, so pushing the definition to the where clause would typically require additional arguments to be passed. I'd rather not do that, so I'm left choosing between "let" and "fix".
A lot of discussion about haskell code revolves around whether or not a given construction is 'clear'; this has something to do with haskell's almost unparallelled ability to abstract, I suppose. Ultimately, something is clear once you are used to the abstraction, but obfuscated if you're not, so it becomes rather subjective.
I'd agree with that, so perhaps I should have instead asked: Are there other ways, besides "fix" and "let", to write monadic loops that won't easily submit to forM and friends? I already see one suggestion from Claus [1], who seems determined that lazy I/O should have the last laugh :-), that is, extract the loop structure to a new combinator, leaving the loop body inline. [1]http://www.haskell.org/pipermail/haskell-cafe/2007-March/023686.html