Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reading and representing input which specifies the data type to use

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:

  1. The type a is chosen by the caller.
  2. readT should produce Nothing if the user's input is incompatible with a.
  3. readT should produce Just (T a) if the input is valid.
  4. 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.

like image 233
sastanin Avatar asked Nov 13 '10 19:11

sastanin


1 Answers

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.

like image 183
mokus Avatar answered Oct 23 '22 04:10

mokus