3 ms·
This is why I've become very fond of a little advertised part of haskell: the where clause. Every function can have its own little library of subfunctions, with
by joeyh 15y ago
This is why I've become very fond of a little advertised part of haskell: the where clause. Every function can have its own little library of subfunctions, without polluting the toplevel namespace.
Whenever code starts getting complicated, I break it out into a subfunction in the where clause, which means I give it a name, which makes my code more self-documenting, and incidentally keeps the indentation level sane. Breaking things out into lots of little functions like this often makes it apparent when the function in the where clause is more generic, and does belong at the toplevel, and then the code is already separated into a function that's easy to move out of the where clause (closures do mean additional parameters sometimes need to be added, but the compiler will make sure you get this right).
The other nice thing haskell brings to the table, which would be more helpful in the first example given, is the ability to write your own control flow functions. For the first example, which keeps trying different actions from a list until one succeeds, and returns its value, I would write a generic function to do that. Its type signature would be:
firstM :: (Monad m) => [m (Maybe a)] -> m (Maybe a)
Of course, I don't need to write that function.. I can just paste the above type signature into Hayoo, and get directed to an existing implementation: http://hackage.haskell.org/packages/archive/darcs/latest/doc/html/Darcs-Utils.html#v:firstJustM http://hackage.haskell.org/packages/archive/darcs/latest/doc...
If I did need to write firstM, I'd feel special to have been the first to think up such a generic and useful function. So it's win-win-win all the way. :)
Anyway, the code to use it would look something like this:
get_cached_user uid name = fromMaybe nouser <$> find
where
cache a = do
r <- a
cache_set_user uid name r
return r
nouser = error "user not found"
find = firstM
[ cache_get_user_by_id uid
, cache_get_user_by_username name
, cache $ db_get_user_by_id uid
, cache $ db_get_user_by_username name
]
As another example of this refactoring of control flow, looking at the cache function above I realized I've written those three lines several times before. So I just added this to my personal library:
observe :: (Monad m) => (a -> m b) -> m a -> m a
observe observer a = do
r <- a
observer r
return r
(I seem to be the first person to think of this function.. yay!)
With this, the "cache" function can be written as just
cache = observe $ cache_set_user uid name