Entropic Thoughts

Non-Obvious Haskell Idiom: ViewPattern Argument Transform

Non-Obvious Haskell Idiom: ViewPattern Argument Transform

Reading production Haskell code, we sometimes stumble over idioms that look confusing at first, but which show up frequently enough that they are worth learning. We have already seen

Today we’ll look at how to transform an argument before naming it. The basic shape of this pattern is to first enable the ViewPatterns extension, and then we can write

someFunction (transform -> usefulValue) =
  ...

instead of what would otherwise be something like

someFunction param =
 let
   usefulValue = transform param
 in
   ...

which leaves a hanging param value that ought not to exist.

Explanation

Let’s say we’re making a game where the player carries some inventory, and this is a set of items for quick membership tests. Our function to print that inventory might have the signature

printInventory :: Set Item -> IO Int

where each item is printed on its own line, and then the integer returned is the width of the inventory listing – i.e. the length of the longest line printed. The simplest implementation of this would be in terms of a list of items1 You could argue we should perform a side-effectful fold over the set instead to find the maximum length printed. That would probably be a better design and side-step the entire conversion problem, but bear with me. Coming up with good examples is hard.:

printInventory itemSet = do
  -- Convert set of items to a list of items.
  let items = Set.toList itemSet

  -- Print all items with their index, collecting the printed width.
  widths <- for (zip [1..] items) $ \(idx, item) -> do
    let printed = show idx <> ". " <> name item
    putStrLn printed
    pure (length printed)

  -- Return the largest of the printed widths, and zero if none were printed.
  pure (foldr max 0 widths)

The reason we need to transform the set to a list first is that sets are not traversable, so the for function does not work on them.

However, this implementation leaves us with a loose itemSet variable that has a value but is not meant to be used in the method. Having variables around that we intend not to use is … perhaps not a code smell, but at least a mildly unpleasant aroma. When writing code, we try to write it so that we name only the things we care about.

This is where we can use view patterns to transform an argument to a function before we give it a name. By moving the Set.toList transformation up into a view pattern, we get to name the result of that transformation and keep the raw value un-named:

printInventory (Set.toList -> items) = do
  widths <- for (zip [1..] items) $ \(idx, item) -> do
    let printed = show idx <> ". " <> name item
    putStrLn printed
    pure (length printed)

  pure (foldr max 0 widths)

Maybe it’s the type of code I’m in the middle of writing2 Wrangling a compatibility layer between two apis., but I find this useful very often.

View patterns can do more but I won’t

I should say that I’m aware view patterns can do actual pattern matching also, i.e. where the thing to the right of the arrow is not just a name but a constructor. I’m just not a fan of defining functions piecewise – I prefer one definition and then an explicit case statement to cover all cases. I would write

printInventory itemList =
  case nonEmpty itemList of
    Nothing -> do
      putStrLn "You are carrying nothing."
      pure 0
    Just items -> do
      -- ...

over

printInventory (nonEmpty -> Nothing) = do
  putStrLn "You are carrying nothing."
  pure 0
printInventory (nonEmpty -> Just items) = do
  -- ...

There’s something pleasing about piecewise definitions, but there’s still the programmer in me that thinks functions should have one entry point and ideally one exit point.