Skip to content

Instantly share code, notes, and snippets.

@passy
Created December 14, 2016 23:24
Show Gist options
  • Save passy/2baae9938c471f7844b32db2fade809d to your computer and use it in GitHub Desktop.
Save passy/2baae9938c471f7844b32db2fade809d to your computer and use it in GitHub Desktop.

I had a fun refactoring example in Haskell today I wanted to share. So, I've got a structure with a nested Maybe inside, which looked like this:

Maybe (Vector.Vector (Maybe (Direction, [Departure])))

I wanted to get that second-level Maybe folded into the first as it didn't provide any semantic meaning. So I start by writing the type definition:

seqVec :: Maybe (Vector.Vector (Maybe (Direction, [Departure])))
       -> Maybe (Vector.Vector (Direction, [Departure]))

When I don't quite know how to approach a problem first, I try and take the easiest, hackiest way first and go with a do block:

seqVec mvec = do
  vec <- mvec
  i <- sequence vec
  return i

Take things out of the box, sequence it, put it back in. Easy enough and pretty clear, but also super redundant. So let's fold the last two lines into one as a first step:

seqVec' mvec = do
  vec <- mvec
  sequence vec

This makes it pretty clear, that the do notation here might be overkill.

seqVec'' mvec = mvec >>= \vec -> sequence vec

Now this is just screaming for eta reduction.

seqVec''' mvec = mvec >>= sequence

One more step and there's barely anything left of this function.

seqVec'''' = (>>= sequence)

And clearly this is useful in a broader scope than just my nested Vector example. What does GHCI infer for this?

λ> :t (>>= sequence)
(>>= sequence)
  :: (Traversable t, Monad m) => m (t (m a)) -> m (t a)

Haskell is one of the few languages where I find refactoring truly enjoyable and I hope this little example shows why.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment