3

I want to implement an instance of read that enables me to read a string (ex: "- - 8 - 3 -") and construct a list containing those values.

data Value a = Nul | Val a

showsValue :: (Show a) => Value a -> ShowS
showsValue (Val x) =  ("Value" ++) . shows x
showsValue (Nul)   =  ("Nothing 0" ++)

instance Show a => Show (Value a) where
    showsPrec _ x = showsValue x

instance Read a => Read (Value a) where
    readsPrec _ m = readsMatrix m

readsMatrix :: (Read a) => ReadS (Value a)
readsMatrix ('-':s) = [(Nul, rest) | (' ',rest) <- reads s]
readsMatrix s       = [(Val x,rest)| (x,' ':rest) <- reads s]

After performing this test:

read "- 8 - - 3" :: Value Int

I get the error *** Exception: Prelude.read: no parse

What am I doing wrong here?

update

readsMatrix ('-':s) = [(Nul, dropWhile isSpace s)]
readsMatrix s = [(Val x, dropWhile isSpace rest) | (x,rest) <- reads s]
1
  • 1
    You might be interested in the answers to this question. Commented Nov 25, 2012 at 20:02

2 Answers 2

2

Correcting read for Value a

First, don't bother removing spaces, that's all handled fine by reads:

readsMatrix :: (Read a) => ReadS (Value a)
readsMatrix ('-':s) = [(Nul, dropWhile (==' ') s)]
readsMatrix s       = [(Val x,rest)| (x,rest) <- reads s] 

Your read instance is now fine for single Values:

*Main> read "4" :: Value Int
Value4
*Main> read "-" :: Value Int
Nothing 0

Correcting read for [Value a]

But you want to read lists as separated by spaces, so since that's non-standard behaviour, you'll need to write a custom readList :: :: ReadS [Value a]

instance Read a => Read (Value a) where
    readsPrec _ m = readsMatrix m
    readList s = [(map read.words $ s,"")]

So now

*Main> read "- 4 2 - 5" :: [Value Int]
[Nothing 0,Value4,Value2,Nothing 0,Value5]

but unfortunately,

*Main> read "- 4 2 \n- 5 4" :: [Value Int]
[Nothing 0,Value4,Value2,Nothing 0,Value5,Value4]

and even worse,

*Main> read "- 4 2 \n- 5 4" :: [[Value Int]]
*** Exception: Prelude.read: no parse

Reading a matrix

There's no straightforward way round this that I can see, because there's no readListOfLists in the Read class, so why not make a standalone function

matrix :: Read a => String -> [[Value a]]
matrix = map read.lines

so that

*Main> matrix "3 4 -\n3 - 6\n4 5 -" :: [[Value Int]]
[[Value3,Value4,Nothing 0],[Value3,Nothing 0,Value6],[Value4,Value5,Nothing 0]]

Show isn't what I would choose

I think your Show instance for Value is a little misleading (Nul doesn't have a zero on it, Val isn't written Value). Either write

data Value a = Nul | Val a  deriving Show

so that it looks as it is or define it to match the Read instance

instance Show a => Show (Value a) where
  show Nul = "-"
  show (Val a) = show a
Sign up to request clarification or add additional context in comments.

Comments

2

In your readsMatrix function, you have

readsMatrix ('-':s) = [(Nul, rest) | (' ',rest) <- reads s]

That means after the '-' is consumed, the remainder of the input string is passed to

reads :: ReadS Char

but the Read instance for Char expects a character enclosed in single quotes, like 'h'.

To get an element (' ',rest) in the result, you need - after optional whitespace before it - the character sequence '\'' : ' ' : '\'' : whatever. You don't have those in your expected input. What you want to do there is removing the space after the '-', so if the space is optional, and may be more than one character long,

readsMatrix ('-':s) = [(Nul, dropWhile isSpace s)]

would work (if newlines, tabs etc. are also to be ignored). If you just want one mandatory space, the pattern should be

readsMatrix ('-':' ':s) = [(Nul, s)]

The second equation

readsMatrix s       = [(Val x,rest)| (x,' ':rest) <- reads s]

may work as is, but requires that values are followed by a space, so for example readMatrix "3" :: [(Value Int, [Char])] would return an empty list.

I think you'd rather have the space optional there too, so maybe

readsMatrix s = [(Val x, dropWhile isSpace rest) | (x,rest) <- reads s]

is what you want, but since reads ignores leading whitespace for most types,

readsMatrix s = [(Val x, rest) | (x, rest) <- reads s]

may be better.

But you're not ignoring leading whitespace before a '-', so that would need to be done too.

Maybe the implementation would be better done using

lex :: ReadS String

from the Prelude, that splits a string into tokens roughly following the Haskell syntax, e.g.

ghci> lex "(12 + 34)  abc"
[("(","12 + 34)  abc")]

splits off the opening parenthesis, from the remainder the token "12" would be split off etc.

One possibility using lex, so that leading whitespace is duly ignored would be

readsMatrix s = do
        (tok, rest) <- lex s
        case tok of
           "-" -> return (Nul, rest)
           _   -> case reads tok of
                    [(x,"")] -> return (Val x, rest)
                    _ -> []

Finally,

read "- 8 - - 3" :: Value Int

would lead to a *** Exception: Prelude.read: no parse even then, because read only succeeds if there is only whitespace following the converted token, but after converting the initial '-' there are still four more convertible tokens in the remainder of the input.

read "- 8 - - 3" :: [Value Int]

on the other hand should succeed.

2 Comments

Thank you for the excellent explanation. I tried what you suggested, but I have problems when I try to read a string with more then one element, because of the white spaces before the convertable tokens. I tried with the types [Value Int], but that didn't work either.
I've added a quick implementation using lex to ignore leading spaces. Another option would be case dropWhile isSpace s of { '-':rest -> [(Nul, rest)]; other -> [(Val x, rest) | (x,rest) <- reads other] }.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.