JeanJouX
JeanJouX

Reputation: 2721

Read instance of a combined datatype with Haskell

I implemented these (simplified) datatypes in a Haskell program :

data Type  = ValA
           | Valb
           | ValC

data Prefix = PrefA
            | PrefB
            | PrefC
            | NoPref

data Combin = Combin Prefix Type

instance Show Type where
  show ValA = "typeA"
  show Valb = "O"
  show ValC = "mp"

instance Read Type where
  readsPrec  _ "typeA" = [(ValA,"")]
  readsPrec  _ "O"     = [(Valb,"")]
  readsPrec  _ "mp"    = [(ValC,"")]

instance Show Prefix where
  show PrefA = "a"
  show PrefB = "bm"
  show PrefC = "c"
  show NoPref = ""

instance Read Prefix where
  readsPrec  _ "a"  = [(PrefA,"")]
  readsPrec  _ "bm" = [(PrefB,"")]
  readsPrec  _ "c"  = [(PrefC,"")]
  readsPrec  _ ""   = [(NoPref,"")]

instance Show Combin where
  show (Combin pre typ) = show pre++show typ

With the instances, I'm able to show and read the types Prefix and Type. The Combin datatype is a concatenation of a Prefix and a Type. Now, I would like to implement the read instance for the Combin datatype and I have no idea on how to do it.

I thought about deriving the Combin type, but it cause the output string of Combin PrefA ValC to be "Combin a mp". And it's not what I want. I want "amp" concatened together. Same thing for read

I thought about making pattern matching with the input string but the Prefix strings have differents length and may be void (NoPref).

Have you ever implemented a such functionnality with read ? Do you know how to do it ?

Upvotes: 0

Views: 91

Answers (1)

Cactus
Cactus

Reputation: 27626

Your readsPrec implementations are wrong, since they are supposed to accept valid prefixes of the input, not necessarily consume the whole input. Your readsPrec functions are not composable.

The key to the solution is to rewrite them so that they check if their input matches any of the names:

import Data.List (stripPrefix)

instance Read Type where
    readsPrec _ s | Just s' <- stripPrefix "typeA" s = [(ValA, s')]
                  | Just s' <- stripPrefix "O" s     = [(Valb, s')]
                  | Just s' <- stripPrefix "mp" s    = [(ValC, s')]
                  | otherwise = []

and similarly for Prefix:

instance Read Prefix where
    readsPrec _ s | Just s' <- stripPrefix "a" s  = [(PrefA, s')]
                  | Just s' <- stripPrefix "bm" s = [(PrefB, s')]
                  | Just s' <- stripPrefix "c" s  = [(PrefC, s')]
                  | otherwise = [(NoPref, s)]

Note the last branch of readsPrec for Prefix, which says that every string that doesn't begin with "a", "bm" or "c" is to be parsed as NoPref. This only works for Combin because there's no Type which starts with any of the Prefix bits; otherwise, the parser for Prefix would need to be nondeterminstic, since a string like "aXY" could either correspond to a Prefix of "a" and a Type of "XY", or a Prefix of NoPref and a Type of "aXY". I went with the deterministically eager version here because I think it's going to be easier for you to understand how it works.

Once we have these two Read instances, writin ghte one for Combin is a straightforward matter of trying to read the whole string with reads for Prefix, and then trying to read each remainder as a Type:

instance Read Combin where
    readsPrec _ s = [(Combin pre typ, s'') | (pre, s') <- reads s, (typ, s'') <- reads s']

If you are familiar with do notation and the [] monad, you can rewrite this as

instance Read Combin where
    readsPrec _ s = do
        (pre, s) <- reads s
        (typ, s) <- reads s
        return (Combin pre typ, s)

Upvotes: 2

Related Questions