I'm trying to learn Haskell and I thought I would try and replicate the Linux 'xxd' utility. However I am stuck on the right-hand column (which displays ASCII or a blank for non-printing characters).
As a reminder, typical xxd output looks like this:
00000000: 4927 6d20 7472 7969 6e67 2074 6f20 6c65 I'm trying to le
00000010: 6172 6e20 4861 736b 656c 6c2e 2041 7320 arn Haskell. As
...
I also want to take advantage of the Unicode "Control Pictures" block to display little symbols for the control codes 0..31 rather than dots or placeholders. So I have a helper function that converts a Word8 to a Char and at the same time replaces the control characters with the equivalent from the Control Pictures block.
http://www.unicode-symbol.com/block/Control_Pictures.html
Constraints: Eventually the program will read a file from disk so I am expecting to read in a ByteString or lazy ByteString. Also I want to use Data.Text to hold the output rather than String.
Ideally: I would like to avoid converting the ByteString to something else wholesale eg [Word8] because - ultimately - I need to learn how to work with it, not around it.
My problem is that I can't get map
to work for me. Neither B.map nor T.map will work because they expect a function ([a] -> [a])
. Prelude.map
looks more promising as it expects ([a] -> [b])
but I can't get it to work with the imported types. So I had a go at defining my own map
(just to try and find something that would work which I could then replace with a built-in function when I had a better understanding) but that isn't working either.
The non-functioning code I have is as follows
import qualified Data.ByteString as B
import qualified Data.Text as T
import Data.Word
import Data.Char
{- Make some normally undisplayable bytes into displayable chars -}
displayableChar :: Word8 -> Char
displayableChar w
| i < 32 = chr (0x2400 + i) -- 0x2420 control codes
| i < 33 = chr 0x2423 -- 0x2423 trough for space
| i < 127 = chr i
| i < 128 = chr 0x2421 -- 0x2421 del
| otherwise = ' '
where i = fromIntegral w
mymap :: (Word8 -> Char) -> B.ByteString -> [Char]
mymap f bstr
| bstr == B.empty = []
| otherwise = f x : map f xs
where
x = B.head bstr
xs = B.tail bstr
test_data = B.pack [1..250]
Advice and suggestions welcome please on what is the 'right' way to apply displayableChar
to each byte in the ByeString and get Text out.
You wrote map
instead of mymap
in your function definition:
mymap :: (Word8 -> Char) -> B.ByteString -> [Char]
mymap f bstr
| bstr == B.empty = []
| otherwise = f x : mymap f xs -- <- here
where
x = B.head bstr
xs = B.tail bstr
Another way is to use B.unwrap
to get a list of Word8
so you can then apply map
which works on lists:
mymap :: (Word8 -> Char) -> ByteString -> [Char]
mymap f bs = map f (B.unwrap bs)
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.