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.

Comments

AntC

Everything about ViewPatterns is non-obvious Haskell. It’s an awful so-called feature that should have been withdrawn as soon as PatternSynonyms were available. I suggest you recode your examples to use PatSyns, then see how much more readable they become.

I do see that the current syntax for declaring PatSyns can be cumbersome. There is a more elegant design sketch for that.

(I got curious and poked AntC about what it would look like in practice, and this was his continued response.)

pattern FromSet :: [a] -> Set.Set a
pattern FromSet xs <- (Set.toList -> xs)

printInventory (FromSet items) = do
  widths <- for (zip [1..] items) $ \(idx, item) -> do

When used, this looks like a regular Haskell 98 pattern match on the left hand side of a function equation, and that has practical benefits: you don’t need to enable pattern synonyms at the usage site. The usage site also has shorter code and avoids the floating ->.

Some reasons to think the view pattern -> syntax is weird: In all other Haskell binding syntax, the bound variables/pattern are to the left of the <- arrow or = equals sign – in fact, this is the case for nearly every programming language since Algol 60 ;-). (The designers of ViewPatterns more-or-less admit Haskell is being idiosyncratic here; there was a mirror-image syntax considered.) In addition, all other uses of -> in Haskell term syntax has the left hand side being a pattern/binding, and the right hand side an expression.

Unfortunately, we still have to use ViewPatterns when declaring a pattern synonym like this, because the designers of pattern synonyms just drew on what existed in the language, but at least the weird floating -> doesn’t escape the pattern synonym definition. My proposal for a better pattern synonym syntax would use guards to declare a bi-directional pattern synonym:

pattern FromSet xs
   | s <- Set.fromList xs
     = s
     | xs <- Set.toList s

Ideally, package authors would include a bunch of pattern synonyms alongside their data declarations, perhaps even instead of conversion functions like fromList and toList, and that would be the main way to convert between data types.