Reputation: 4447
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
Reputation: 32455
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
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
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]]
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
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