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:
((.) . showString . show)
? For example, I know that (f . g) x
can be re-written as f (gx)
. foldr
? 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
f . g . h . id
f . g . h . id
or f . g . h
f . g . h
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}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.