Why does this function used by map need to return a (singleton) list instead of an element?

Viewed 141

Total beginner as you might have guessed. I've gone through "Learn you a Haskell" but I still struggle with even the basics.

I'm looking at this thread Split a number into its digits with Haskell and things start to make sense with this function

digits :: Integer -> [Int]
digits n = map (\x -> read [x] :: Int) (show n)

asked by Daniel.

I understand that show returns my input number as a String, or list of Chars, which then I can modify element by element using map. So, each "x", as a Char, is picked by the anonymous function and read as an Integer and the new list is composed by map, containing all the read integers. Shouldn't then read [x] be read x? Why does every read Char in the list need to be returned as it's own singleton list? It seems to me that this way calling digits 123 should return [[1],[2],[3]], since map will compose everything into a new list anyway and not [1,2,3] as it correctly does. But why?

3 Answers

Let’s have a look at the type of read:

Prelude> :t read
read :: Read a => String -> a

That is, for any type a which is an instance of Read, the read function can be used to convert a String to a member of that type. Note here that the argument of read is a String — not a Char! Thus, in order to convert a Char to some other type using read, you first need to convert the Char to a String. And how do you do that? Well, remember that a String is just a list of Chars: type String = [Char]. Thus, you convert a Char to a String by wrapping it in a list — which is exactly what your code is doing. You can confirm this is necessary yourself in GHCi:

Prelude> read '1' :: Int

<interactive>:10:6: error:
    * Couldn't match type `Char' with `[Char]'
      Expected type: String
        Actual type: Char
    * In the first argument of `read', namely '1'
      In the expression: read '1' :: Int
      In an equation for `it': it = read '1' :: Int
Prelude> read "1" :: Int
1
Prelude> read ['1'] :: Int
1
Prelude> :t ['1']
['1'] :: [Char]
Prelude> :t "1"
"1" :: [Char]
Prelude> :t '1'
'1' :: Char

(I should note that the rest of your summary of the code is totally correct, by the way.)

read :: Read a => String -> a works on a String, not on a Char and here x is a Char.

Indeed, show n constructs a String, which is a list of Chars, since type String = [Char]. If we perform a mapping over the list of Chars, then x is a Char in the mapping function. We wrap it in a singleton list to construct a String with one Character, and then use read to read that string as an Int.

We can however make use of digitToInt :: Char -> Int which is probably more efficient and elegant:

import Data.Char(digitToInt)

digits :: Integer -> [Int]
digits = map digitToInt . show

Ok, it was a stupid question... The singleton list is there because read requires it as input, a String, not a single Char, and the result is indeed a single Int. Baby steps...

Related