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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With