简体   繁体   中英

Parsing an element of variable type in Haskell

I've finally managed to rig my Haskell program so that, at the very end, if I write

  let foo = bar :: A

then I get one behaviour, and if I write

  let foo = bar :: B

then I get the other desired behaviour.

Now I'd like my program to be able to parse this argument at runtime, but I really have no idea how to proceed. Any advice?


Edit: I'd like to parse some kind of (text) configuration file for which I am free to make up the specification/format.
One possible toy example is reading an integer as either an Int or a Double depending on further context provided in the configuration file, something along the lines of the following in the configuration file

barType: Int
barValue: 2

giving me bar = 2 :: Int, and

barType: Double
barValue: 2

giving me bar = 2 :: Double. Here it could be the case that I should be able to accept any type which has a Num instance.
In my case, I have a type class with some methods, and I'd like to parse anything with an instance for that type class; the methods could do something significantly different depending on the exact type. I don't know how I'd go about writing a Read instance for that.

Thanks.

If you are doing this, I presume you have some code whose contents depend on the type of foo, but whose return type does not. Suppose your full code example is

let foo = bar :: Int
in print (foo + 1)

Run-time parameter passing is normally accomplished by functions, so first rewrite the code as a function. The (Show a, Num a) => in the type signature shows that some type class methods are passed as a hidden parameter to the function.

printBar :: (Show a, Num a) => a -> IO ()
printBar x = print ((bar `asTypeOf` x) + 1)

For instance, printBar (undefined :: Double) will use type Double in its body. The compiler finds that the argument has type Double , and it passes in the type class methods for Double .

Now, let's write a function that chooses the actual type of printBar .

invokeBar :: Bool -> (forall a. (Show a, Num a) => a -> b) -> b
invokeBar True  f = f (undefined :: Int)
invokeBar False f = f (undefined :: Double)

Now you can call invokeBar True printBar or invokeBar False printBar to choose whether to use Int or Double .

You cannot read in an "unknown type" from a file, because there is no file format encompassing all possible types. However, you can make a parser that recognizes the names of the types you will use, and use the approach above for each of those names.

If you're looking for different behaviors based on the type being instantiated, then you need a type class. Good examples to study would be Data.Binary instances of even the Read type class.

Eg

class Binary t where

    -- | Decode a value in the Get monad
    get :: Get t

You didn't say what kind of parsing you wanted to do -- text? binary? -- but it might be as simple as just reusing existing parsing libraries, and writing instances for your types.

Alternatively, you could use parser combinators to choose between different options.

The way I understand it the configuration file specifies how the parser further proceeds. A very simple example of this is CSV, where the first line determines the number of fields in all following lines. Assuming that all fields are string fields my variant would be to parse the first line into a parser that parses the following lines:

csvHeader :: Parser (Parser [String])

The result of csvHeader is a parser that is to be applied to all remaining lines:

csvHeader >>= many

Now in your case the actual result can also be one of many types. The simplest way to do this is to have an ADT with the possible outcomes:

data CsvField = Coordinate Float Float | Person Name

A more advanced, safer and more flexible solution is not to think in terms of data, but in terms of the operations you can perform on them. Say you are parsing a configuration file that contains a number, either an integer or a floating point number. In either case you want to print the number to the screen. So instead of returning the actual number, return the operation:

config :: Parser (IO ())

Finally if you want to keep some information about whatever is contained in the field while keeping the type variable, you need to become existential:

data Field = forall a. Field a (a -> a)

Now you can parse arbitrary things, as long as you return a value along with a function to apply to it:

config :: Parser Field

Using your example input:

barType: Int
barValue: 2

Since barType tells you how to interpret barValue , you need to either parse both lines in at the same time, or maintain state (using State or StateT ) while parsing. In either case, you basically need to pattern-match on the type. For example, if I were using Data.Binary.Get for parsing, I'd have something like:

data Bar = IntBar Int | DoubleBar Double

parseBar :: String -> Get Bar
parseBar t = case t of 
  "Int" -> liftM IntBar $ get
  "Double" -> liftM DoubleBar $ get

Point being, no matter how you slice it, you need to know what types you'll be working with ahead of time, because your parser needs to account for them all. This also implies that a type class representing all the possible Bar -like things is probably not the way to go; you probably want a simple Bar ADT instead.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM