Here's how I'd write your code:
increasing :: Integer -> [Integer]
increasing 1 = [1..9]
increasing n = let allEndings x = map (10*x +) [x `mod` 10 .. 9]
in concatMap allEndings $ increasing (n - 1)
I arrived at this code as follows. The first thing I did was to use pattern matching instead of guards, since it's clearer here. The next thing I did was to eliminate the liftM2
s. They're unnecessary here, because they're always called with one size-one list; in that case, it's the same as calling map
. So liftM2 (*) ps [10]
is just map (* 10) ps
, and similarly for the other call sites. If you want a general replacement for liftM2
, though, you can use Control.Applicative
's <$>
(which is just fmap
) and <*>
to replace liftMn
for any n
: liftMn f a b c ... z
becomes f <$> a <*> b <*> c <*> ... <*> z
. Whether or not it's nicer is a matter of taste; I happen to like it.1 But here, we can eliminate that entirely.
The next place I simplified the original code is the do ...
. You never actually take advantage of the fact that you're in a do
-block, and so that code can become
let ps = increasing (n - 1)
last = map (`mod` 10) ps
next = map (* 10) ps
in alternateEndings next last
From here, arriving at my code essentially involved writing fusing all of your map
s together. One of the only remaining calls that wasn't a map
was zipWith
. But because you effectively have zipWith alts next last
, you only work with 10*p
and p `mod` 10
at the same time, so we can calculate them in the same function. This leads to
let ps = increasing (n - 1)
in concat $ map alts ps
where alts p = map (10*p +) [y `mod` 10..9]
And this is basically my code: concat $ map ...
should always become concatMap
(which, incidentally, is =<<
in the list monad), we only use ps
once so we can fold it in, and I prefer let
to where
.
1: Technically, this only works for Applicative
s, so if you happen to be using a monad which hasn't been made one, <$>
is `liftM`
and <*>
is `ap`
. All monads can be made applicative functors, though, and many of them have been.