wizzup
wizzup

Reputation: 2411

Understanding Read instance

I made Read and Show instances of my data, but did not understand the Read instance

data Tests = Zero
          | One Int
          | Two Int Double

instance Show Tests where
  show Zero      = "ZERO"
  show (One i)   = printf "ONE %i" i
  show (Two i j) = printf "TWO %i %f" i j

instance Read Tests where
  readsPrec _ str   = [(mkTests str, "")]

mkTests :: String -> Tests
mkTests = check . words

check :: [String] -> Tests
check ["ZERO"]      = Zero
check ["ONE", i]    = One (read i)
check ["TWO", i, j] = Two (read i) (read j)
check _             = error "no parse"

main :: IO ()
main = do
  print Zero
  print $ One 10
  print $ Two 1 3.14

  let x = read "ZERO" :: Tests
  print x
  let y = read "ONE 2" :: Tests
  print y
  let z = read "TWO 2 5.5" :: Tests
  print z

This is output

ZERO         
ONE 10       
TWO 1 3.14   
ZERO         
ONE 2        
TWO 2 5.5  

Here are questions:

  1. What is recommend way to implement Read instance?

    • The minimal complete definition of Read class is readsPrec | readPrec and readPrec :: ReadPrec a description wrote

    Proposed replacement for readsPrec using new-style parsers (GHC only).

    • Should I use readPrec instead, How? I can't find any example on the net that I can understand.
    • What is the new-style parsers, is it parsec?
  2. What is the first Int argument of readsPrec :: Int -> ReadS a , is using for?

  3. Is there anyway to somehow deriving Read from Show?

In the past I could use deriving (Show,Read) to most of the job. But this time I want to move to next level.

Upvotes: 3

Views: 1769

Answers (1)

Devin Lehmacher
Devin Lehmacher

Reputation: 106

  1. In my opinion the correct way to implement Read is to derive it and otherwise, it is likely better to move on to more sophisticated parsers. Here is answers to all of your questions anyways.
    • readPrec is a simple parser combinator based approach for that GHC provides. If you are willing to sacrifice portability for your Read instance you can use it and it makes parsing easier.
    • I include a small example of how you could use readPrec below
    • parsec is different from readPrec, however both are parser combinator like. Parsec is a much more complete parser library. Another parser combinator library is attoparsec which works very similarly to parsec.
    • parsec and attoparsec can't be used with the ordinary Read typeclass (at least directly) but the greater flexibility they offer makes them a good idea for any time you want more complex parsing.
  2. The Int argument to readsPrec is for dealing with precedence when parsing. This might matter when you want to parse arithmetic expressions. You can choose to fail parsing if the precedence is higher than the precedence of the current operator.
  3. Deriving Read from Show isn't possible unfortunately.

Here are a couple of snippets that show how I would implement Read using ReadPrec.

ReadPrec example:

instance Read Tests where
  readPrec = choice [pZero, pOne, pTwo] where
    pChar c = do
      c' <- get
      if c == c'
      then return c
      else pfail
    pZero = traverse pChar "ZERO" *> pure Zero
    pOne = One <$> (traverse pChar "ONE " *> readPrec)
    pTwo = Two <$> (traverse pChar "TWO " *> readPrec) <*> readPrec

In general implementing Read is less intuitive than more heavyweight parsers. Depending on what you want to parse I highly suggest learning parsec or attoparsec since they are extremely useful when you want to parse even more complicated things.

Upvotes: 1

Related Questions