Home > database >  Haskell list has is showing number and empty string when mapping
Haskell list has is showing number and empty string when mapping

Time:04-05

I am reading a file, this file contains hexadecimal bytes. I read this into a string, I map them into single words and then I turn them into their decimal values.

main = do
  args <- getArgs
  contents <- readFile (head args)
  let singlewords = words contents
  let intContents = map readHex singlewords

When I print the output of intContents, I get:

[[(5,"")],[(0,"")],[(0,"")],[(0,"")],[(5,"")],[(0,"")],[(0,"")],[(0,"")],[(48,"")],[(28,"")],[(75,"")],[(201,"")],[(134,"")],[(0,"")],[(0,"")]]

These are the proper decimal values, but why are they paired with an empty string? and as separate nested lists? I just want the values like:

[5,0,0,0,5,...]

I tried to just do:

intContents <- readHex contents

But I still only get:

[(5," 00 00 00 05 00 00 00\n30 1C 4B C9 86 00 00")]

This just does the first term. Any help would be appreciated.

Edit

Now understanding the types readHex returns, how can I use pattern matching with my code to get just the decimal number.

Some sort of definition like this?

intContents :: [(a, b)] -> a
intContents [(a, b)] = a

or do I just put the pattern in the mapping like this?

let intContents = map ([(a, b)] (readHex singleWords)

Note: I tried both of these and just have errors I don't necessarily understand

Again any help is appreciated!

CodePudding user response:

readHex has the type

readHex :: (Eq a, Num a) => ReadS a 

which in turn is (see here)

type ReadS a = String -> [(a, String)] 

So readHex in your example seems to do exactly what it promises to do and explains the pairs. But what exactly does the output mean? The page linked above tells us

A parser for a type a, represented as a function that takes a String and returns a list of possible parses as (a,String) pairs.

So in your case these parses all result in an unique value, which is why you get these pairs as single items of a list. You could extract those values easily by pattern matching using:

map (\[(a,_)] -> a) 

But you might to put a little bit more effort into this extraction as it will error if you don't exactly match [(a, _)].

CodePudding user response:

how can I use pattern matching with my code to get just the decimal number.

It can't be done, in general, because sometimes there will be no decimal number! For example:

> readHex "lol"
[]

So to do a good job of this task, you must decide what you want to happen in surprising cases. This is, in my opinion, the type system helping you to think about what code needs to be written.

One choice you could make would be like this: if any of the contents of the file aren't hex numbers, print a message saying so and exit without doing anything else. This is about the coarsest-grained response you could have; to accomplish this task we need only remember whether there was an error so far or not. We can do this using Maybe; as usual with lists, the patterns we need to match are [] and _:_. So:

readHexMaybe :: String -> Maybe Int
readHexMaybe s = case readHex s of
    [] -> Nothing
    (n, s') : otherResults -> Just n

If you write this code, and turn on warnings, you'll get told that s' and otherResults aren't used. And that does seem like something worth thinking about! In the s' position, a successful parse will give us an empty string; and in the otherResults position, a successful parse will give us an empty list. We should think about what to do in other cases -- the type system helping us again!

Continuing our plan of demanding that everything go perfectly, we could expand this:

readHexMaybe :: String -> Maybe Int
readHexMaybe s = case readHex s of
    [] -> Nothing
    (n, "") : [] -> Just n

Now we get a warning saying not all cases are covered. Okay, what should happen if the s' position or otherResults position are not empty? Presumably Nothing again. So:

readHexMaybe :: String -> Maybe Int
readHexMaybe s = case readHex s of
    [] -> Nothing
    (n, "") : [] -> Just n
    (n, _) : _ -> Nothing

In fact, it's simpler to put the successful case first and return Nothing in all other cases. We can also contract the syntax sugar of [x] for x:[].

readHexMaybe :: String -> Maybe Int
readHexMaybe s = case readHex s of
    [(n, "")] -> Just n
    _ -> Nothing

Okay, we have a simpler type for reading now. If we want to gather up all the failures from a whole collection of such parses, we can use mapM. So:

readAllHexMaybe :: [String] -> Maybe [Int]
readAllHexMaybe = mapM readHexMaybe

Finally, in main, we can turn Nothings into a message for the user, and Justs into a continuation that does something interesting with the list of [Int]s.

main = do
    args <- getArgs
    fileName <- case args of
        [fileName] -> return fileName
        _ -> die "USAGE: ./whatever FILE"
    contents <- readFile fileName
    case readAllHexMaybe (words contents) of
        Nothing -> die "File contained things that didn't look like hex numbers"
        Just ns -> {- ... -}
  • Related