Question

I came across something that seems very strange to me, coming from the compiler's suggestions.

I made a data type to represent binary numbers as follows:

data Bin = Zero | One

I chose to represent multiple-digit binary numbers as lists of type Bin, like so:

myNum :: [Bin]
myNum = [One, Zero, One, One] -- represents the number 1011

Naturally, I want to display my binary numbers in a more convenient way, so I made an instance of Show to do this for me:

instance Show Bin where
    show Zero = "0"
    show One  = "1"
    showList []     = showString ""
    showList (x:xs) = showString (show x) . showList xs

This works, and print myNum correctly displays 1011.

I'm still fairly new at Haskell, and this is my first time working with showList. But this makes sense to me. Since showList has type [a] -> ShowS (which is itself an alias for [a] -> (String -> String)), I understand that I "concatenate" the elements of a list by using function composition. But the compiler suggested that I take the showList function and redefine it:

Warning: Use foldr
  Found:
    showList [] = showString ""
    showList (x : xs) = showString (show x) . showList xs
  Why not:
    showList xs = foldr ((.) . showString . show) (showString "") xs

And, once I had replaced what it suggested, it made a further suggestion:

Error: Eta reduce
  Found:
    showList xs = foldr ((.) . showString . show) (showString "") xs
  Why not:
    showList = foldr ((.) . showString . show) (showString "")

I understand the Eta reduce error, since it's usually preferable to write point-free functions. But I'm having trouble with the first conversion. I see that the second argument to foldr is the "base case" right identity, but I'm having difficulty understanding what is going on in the first argument to foldr. So I have two questions:

  1. How could I rewrite ((.) . showString . show)? For example, I know that (f . g) x can be re-written as f (g x).
  2. What does this actually do in the context of foldr?

No correct solution

OTHER TIPS

How about rewriting it as follows, first let's break out the pattern of composition into a separate function

 compose :: [a -> a] -> a -> a
 compose = foldr (.) id

You can visualize this as taking a list f : g : h : [] and replacing [] with id and : with ., leaving you with f . g . h . id or f . g . h.

Next let's use this to rewrite your example to make it a bit more legible

  instance Show Bin where
    showList = compose . map (showString . show)

Now this is a bit more legible than what you had even though it's functionally identical. In fact this may end up even compiling as the same thing since GHC may fuse it(?). we're transforming each item into a String -> String or ShowS, and then we're composing them all. Giving a name and type to compose makes it much easier to see what's going on.

I suppose this could also be written

 showList = appEndo . mconcat . map (Endo . showString . show)

This is identical to the above but relies on the fact that a -> a forms a monoid and mconcat generalizes the idea of combining a list of monoids to one. We need that Endo bit because the monoid instance is actually defined for

newtype Endo a = End {appEndo :: a -> a}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top