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 Nothing
s into a message for the user, and Just
s 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 -> {- ... -}