2010-11-13 8 views
5

Me gustaría leer algunos datos que a su vez especifican el tipo de datos a usar.Lectura y representación de entrada que especifica el tipo de datos a usar

Por ejemplo, vamos a suponer que puede haber entradas del usuario como éstas:

integer pair 1 2 
integer triple 1 2 3 
real pair 1 2 
real triple 1 2 3 

y hay un tipo de datos para representarla:

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) 

donde los tipos de valores permitidos tienen que pertenecer a MValue clase:

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 

me puede escribir fácilmente los lectores para Pair s y Triple s:

readPair (w1:w2:[]) = Pair <$> maybeRead w1 <*> maybeRead w2 
readTriple (w1:w2:w3:[]) = Triple <$> maybeRead w1 <*> maybeRead w2 <*> maybeRead w3 

El problema es ¿Cómo se escribe un lector polimórfica para toda la T a tipo:?

que quiero:

  1. El tipo a es elegido por la persona que llama.
  2. readT debe producir Nothing si la entrada del usuario es incompatible con a.
  3. readT debe producir Just (T a) si la entrada es válida.
  4. Los números se deben leer como enteros o como dobles según la entrada.

Una implementación ingenua

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 

da un error en la línea 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. 

El error desaparece si quito esta comprobación, pero ¿cómo puedo comprobar si la entrada de usuario es compatible con el tipo de datos elegido por la persona que llama? Una solución podría ser tener readTInt y readTDouble separados, pero me gustaría que el mismo readT funcione polimórficamente de la misma manera que read.

Respuesta

5

El problema es que el a en undefined :: a no es lo mismo a como los de la firma readT 's. Hay una extensión de idioma disponible en GHC que permite eso, llamada "ScopedTypeVariables". Una solución más portátil sería introducir un poco de código extra para atar de manera explícita los tipos juntos, por ejemplo:

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 

Ésta es una modificación muy rápida y sucia de su código, y estoy se podrían hacer los cambios más elegante, pero eso debería funcionar.

+0

Gracias. Esto funciona. El truco con 'resultado' como argumento para indefinido' Maybe (T a) -> a' es inesperado. – sastanin

+1

En realidad, 'result' y' undefined' son argumentos para 'const :: a -> Maybe (T a) -> a', lo que obliga a que sus tipos se relacionen de la forma indicada por la firma de tipo explícita en' const '. Pero sí, '(indefinido :: Maybe (T a) -> a) result' también funcionaría. – mokus

Cuestiones relacionadas