JasperTack
JasperTack

Reputation: 4447

read instance causes parse error

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]

Upvotes: 3

Views: 1019

Answers (2)

AndrewC
AndrewC

Reputation: 32455

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

Upvotes: 2

Daniel Fischer
Daniel Fischer

Reputation: 183873

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.

Upvotes: 2

Related Questions