I’d like to read some data which itself specifies the data type to use.
For example, let’s assume there may be user inputs like these:
integer pair 1 2
integer triple 1 2 3
real pair 1 2
real triple 1 2 3
and there is a data type to represent it:
data (MValue a) => T a = TP (Pair a) | TT (Triple a)
deriving (Show, Eq)
data Pair a = Pair a a deriving (Show, Eq)
data Triple a = Triple a a a deriving (Show, Eq)
where the allowed value types have to belong to MValue class:
class (Num a, Read a) => MValue a where
typename :: a -> String
readval :: [String] -> Maybe a
instance MValue Int where
typename _ = "integer"
readval [s] = maybeRead s
readval _ = Nothing
instance MValue Double where
typename _ = "real"
readval [s] = maybeRead s
readval _ = Nothing
maybeRead s =
case reads s of
[(x,_)] -> Just x
_ -> Nothing
I can easily write readers for Pairs and Triples:
readPair (w1:w2:[]) = Pair <$> maybeRead w1 <*> maybeRead w2
readTriple (w1:w2:w3:[]) = Triple <$> maybeRead w1 <*> maybeRead w2 <*> maybeRead w3
The problem is how do I write a polymorphic reader for the entire T a type?:
readT :: (MValue a, Read a) => String -> Maybe (T a)
I want:
- The type
ais chosen by the caller. readTshould produceNothingif the user’s input is incompatible witha.readTshould produceJust (T a)if the input is valid.- Numbers should be read as integers or as doubles depending on the input.
A naive implementation
readT :: (MValue a, Read a) => String -> Maybe (T a)
readT s =
case words s of
(tp:frm:rest) ->
if tp /= typename (undefined :: a)
then Nothing
else case frm of
"pair" -> TP <$> readPair rest
"triple" -> TT <$> readTriple rest
_ -> Nothing
_ -> Nothing
gives an error in the line if tp /= typename (undefined :: a):
rd.hs:45:17:
Ambiguous type variable `a' in the constraint:
`MValue a' arising from a use of `typename' at rd.hs:45:17-41
Probable fix: add a type signature that fixes these type variable(s)
Failed, modules loaded: none.
The error goes away if I remove this check, but how can I verify if the user input is compatible with the data type chosen by the caller? A solution might be to have separate readTInt and readTDouble, but I’d like the same readT to work polymorphically the same way as read does.
The problem is that the
ainundefined :: ais not the sameaas the ones inreadT‘s signature. There is a language extension available in GHC that enables that, called “ScopedTypeVariables”. A more portable fix would be to introduce a little extra code to explicitly tie the types together, for example:This is a very quick and dirty modification of your code, and I’m the changes could be made more elegantly, but that should work.