Reputation: 41481
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 Pair
s and Triple
s:
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:
a
is chosen by the caller.readT
should produce Nothing
if the user's input is incompatible with a
.readT
should produce Just (T a)
if the input is valid.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.
Upvotes: 6
Views: 410
Reputation: 3500
The problem is that the a
in undefined :: a
is not the same a
as the ones in readT
'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:
readT :: (MValue a, Read a) => String -> Maybe (T a)
readT s = result
where
result =
case words s of
(tp:frm:rest) ->
if tp /= typename ((const :: a -> Maybe (T a) -> a) undefined result)
then Nothing
else case frm of
"pair" -> TP <$> readPair rest
"triple" -> TT <$> readTriple rest
_ -> Nothing
_ -> Nothing
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.
Upvotes: 6